diff --git a/releases/0.37/404.html b/releases/0.37/404.html new file mode 100644 index 00000000000..db93f09777f --- /dev/null +++ b/releases/0.37/404.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/releases/0.37/circle.yml b/releases/0.37/circle.yml new file mode 100644 index 00000000000..56ad41b2f14 --- /dev/null +++ b/releases/0.37/circle.yml @@ -0,0 +1,4 @@ +general: + branches: + ignore: + - gh-pages diff --git a/releases/0.37/css/react-native.css b/releases/0.37/css/react-native.css new file mode 100644 index 00000000000..4fdc9138d24 --- /dev/null +++ b/releases/0.37/css/react-native.css @@ -0,0 +1,1966 @@ +html { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-family: proxima-nova, "Helvetica Neue", Helvetica, Arial, sans-serif; + color: #484848; + line-height: 1.28; +} + +body { + background-color: #F5FCFF; +} + +p { + margin: 0 0 10px; +} + +.subHeader { + font-size: 21px; + font-weight: 300; + line-height: 30px; + margin-bottom: 10px; +} + +em { + font-style: italic; +} + +h1, h2, h3, h4, h5, h6 { + margin: 10px 0; + font-family: inherit; + font-weight: bold; + line-height: 20px; + color: inherit; + text-rendering: optimizelegibility; +} + +h1 small, h2 small, h3 small, h4 small, h5 small, h6 small { + font-weight: normal; + color: #7b7b7b; +} + +h1, h2, h3, h4 { + line-height: 40px; +} + +h1 { + font-size: 39px; +} + +h2 { + font-size: 31px; +} + +h3 { + font-size: 23px; +} + +h4 { + font-size: 17px; +} + +h5 { + font-size: 14px; +} + +h6 { + font-size: 11px; +} + +h1 small { + font-size: 24px; +} + +h2 small { + font-size: 18px; +} + +h3 small { + font-size: 16px; +} + +h4 small { + font-size: 14px; +} + +img { + max-width: 100%; + height: auto; +} + +ul, ol { + margin: 0 0 10px 25px; + padding: 0; +} + +ul ul, ul ol, ol ol, ol ul { + margin-bottom: 0; +} + +li { + line-height: 20px; +} + +a { + color: #05A5D1; + text-decoration: none; +} + +a:hover, a:focus { + color: #0485A9; + text-decoration: underline; +} + +a:focus { + outline: thin dotted #333; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} + +.center { + text-align: center; +} + +html * { + color-profile: sRGB; + rendering-intent: auto; +} + +.example-container { + position: relative; +} + +.embedded-simulator, .embedded-simulator * { + box-sizing: border-box; +} + +.embedded-simulator p { + text-align: center; + color: #999; +} + +.embedded-simulator { + width: 210px; + position: absolute; + right: -200px; + top: 0; +} + +@media screen and (max-width: 680px) { + .embedded-simulator { + position: relative; + right: 0; + } +} + +.prism { + white-space: pre-wrap; + font-family: 'source-code-pro', Menlo, 'Courier New', Consolas, monospace; + font-size: 13px; + line-height: 20px; + border-left: 4px solid #05A5D1; + padding: 5px 10px; + background-color: rgba(5, 165, 209, 0.05); + overflow: auto; +} + +.prism + .prism { + margin-top: 10px; +} + +.token.keyword { + color: #1990B8; +} + +.token.string, .token.regex { + color: #2F9C0A; +} + +.token.boolean, .token.number { + color: #C92C2C; +} + +.token.comment { + color: #7D8B99; +} + +.side-by-side { + overflow: hidden; +} + +.side-by-side > div { + width: 460; + margin-left: 0; + float: left; +} + +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + border: none; + margin: 0; + padding: 0; +} + +.left { + float: left; +} + +.right { + float: right; +} + +.container { + padding-top: 50px; + min-width: 1160px; +} + +.wrap { + max-width: 1260px; + margin: 0 auto; + padding: 0 20px; +} + +.skinnyWrap { + width: 690px; + margin-left: auto; + margin-right: auto; + padding-left: 20px; + padding-right: 20px; +} + +hr { + height: 0; + border-top: 1px solid #ccc; + border-bottom: 1px solid #eee; +} + +ul, li { + margin-left: 20px; +} + +h1 .anchor, h2 .anchor, h3 .anchor, h4 .anchor, h5 .anchor, h6 .anchor { + margin-top: -50px; + position: absolute; +} + +h1:hover .hash-link, h2:hover .hash-link, h3:hover .hash-link, h4:hover .hash-link, h5:hover .hash-link, h6:hover .hash-link { + visibility: visible; +} + +.hash-link { + color: #aaa; + visibility: hidden; +} + +.nav-main { + *zoom: 1; + background: #3B3738; + color: #fafafa; + position: fixed; + top: 0; + min-height: 50px; + box-shadow: 0 0 5px rgba(0, 0, 0, 0.5); + width: 100%; + z-index: 100; +} + +.nav-main:before, .nav-main:after { + content: " "; + display: table; +} + +.nav-main:after { + clear: both; +} + +.nav-main a { + color: #e9e9e9; + text-decoration: none; +} + +.nav-main .nav-site-wrapper { + display: inline; +} + +.nav-main .nav-site-internal { + margin: 0 0 0 20px; +} + +.nav-main .nav-site-external { + float: right; + margin: 0 12px 0 0; +} + +.nav-main .nav-site li { + margin: 0; +} + +.nav-main .nav-site a { + box-sizing: content-box; + padding: 0 10px; + line-height: 50px; + display: inline-block; + height: 50px; + color: #ddd; +} + +.nav-main .nav-site a:hover { + color: #fff; +} + +.nav-main .nav-site a.active { + color: #fff; + border-bottom: 3px solid #05A5D1; + background: #333; +} + +.nav-main .nav-home { + font-size: 24px; + line-height: 50px; +} + +.nav-home img { + vertical-align: -9px; + margin-right: 8px; + margin-left: 1px; + width: 34px; +} + +.nav-main a.nav-home { + color: white; +} + +.nav-main ul { + display: inline-block; + vertical-align: top; +} + +.nav-main li { + display: inline; +} + +.nav-main a.nav-version { + font-size: 16px; + font-weight: 800; + color: #05A5D1; + margin-left: 8px; + text-decoration: underline; +} + +@media screen and (max-width: 680px) { + .nav-main .nav-home { + font-size: 20px; + } + + .nav-main a.nav-version { + font-size: 14px; + } + + .nav-main .nav-site-wrapper { + display: block; + overflow: hidden; + } + + .nav-main ul { + display: -webkit-flex; + display: flex; + overflow: hidden; + } + + .nav-main li { + -webkit-flex: 1; + flex: 1; + } + + .nav-main .nav-site li a { + width: 100%; + padding: 0; + text-align: center; + font-size: 14px; + } + + .nav-main .nav-site a.active { + color: #05A5D1; + font-weight: bold; + background-color: transparent; + } + + .nav-main .nav-site-internal { + margin: 0; + width: 100%; + } + + .nav-main .nav-site-external { + position: absolute; + top: 0; + right: 0; + float: none; + } + + .nav-main .nav-site-external li a { + padding: 0 6px; + } +} + +.hero { + background: #2D2D2D; + padding: 50px 0; + color: #FDF3E7; + font-weight: 300; +} + +.hero .text { + font-size: 64px; + text-align: center; +} + +.hero .minitext { + font-size: 16px; + text-align: center; + text-transform: uppercase; +} + +.hero strong { + font-weight: 400; +} + +.buttons-unit { + margin-top: 40px; + text-align: center; +} + +.buttons-unit a { + color: #FA6900; +} + +.buttons-unit .button { + font-size: 24px; + background: #05A5D1; + color: #fafafa; +} + +.buttons-unit .button:active { + background: #0485A9; +} + +.buttons-unit.downloads { + margin: 30px 0; +} + +.nav-docs { + font-size: 14px; + float: left; + width: 210px; + margin: 5px 48px 0 0; +} + +.nav-docs ul { + list-style: none; + margin: 0; + margin-left: 1px; +} + +.nav-docs ul ul { + margin-left: 20px; +} + +.nav-docs li { + margin: 0; +} + +.nav-docs h3 { + text-transform: uppercase; + font-size: 14px; +} + +.nav-docs a { + color: #666; + display: block; +} + +.nav-docs a:hover { + text-decoration: none; + color: #0485A9; +} + +.nav-docs a.active { + color: #0485A9; +} + +.nav-docs h3 { + line-height: 25px; + margin-top: 12px; + margin-bottom: 5px; +} + +.nav-docs .nav-docs-section:first-child h3 { + margin-top: 0; +} + +.nav-docs .nav-docs-section:first-child { + padding-top: 0; + border-top: 0; +} + +.nav-docs .nav-docs-section:last-child { + padding-bottom: 0; + border-bottom: 0; +} + +@media only screen and (max-device-width: 1024px) { + @-webkit-keyframes slide-in { + 0% { top: -30px; opacity: 0; } + 100% { top: 0; opacity: 1; } + } + @-moz-keyframes slide-in { + 0% { top: -30px; opacity: 0; } + 100% { top: 0; opacity: 1; } + } + @-o-keyframes slide-in { + 0% { top: -30px; opacity: 0; } + 100% { top: 0; opacity: 1; } + } + @keyframes slide-in { + 0% { top: -30px; opacity: 0; } + 100% { top: 0; opacity: 1; } + } + + .nav-docs { + position: fixed; + z-index: 90; + top: -100%; + left: 0; + width: 100%; + height: 100%; + margin: 0; + padding: 53px 0 0 0; + background: #3B3738; + } + + .nav-docs-viewport { + border-top: 1px solid rgb(5, 165, 209); + padding: 25px; + overflow: scroll; + -webkit-overflow-scrolling: touch; + position: relative; + width: 100%; + height: 100%; + } + + /* Active state */ + .nav-docs.in { + top: 0; + -webkit-animation: slide-in 0.3s forwards; + -moz-animation: slide-in 0.3s forwards; + -o-animation: slide-in 0.3s forwards; + animation: slide-in 0.3s forwards; + } + + .nav-docs * { + -webkit-font-smoothing: antialiased; + } + + .nav-docs-section + .nav-docs-section { + margin-top: 50px; + } + + .nav-docs-section li { + margin: 5px 0; + } + + .nav-docs-section h3, + .nav-docs-section a { + color: white; + } + + .nav-docs-section h3 { + border-bottom: 1px solid white; + margin-bottom: 10px; + opacity: 0.3; + } + + .nav-docs-section a { + margin-right: 25px; + font-size: 120%; + padding: 5px 0; + } + + .nav-docs-section a.active { + border-bottom-style: solid; + border-bottom-width: 1px; + color: rgb(5, 165, 209); + } +} + +/** + * Multicolumn layout for phone (landscape only) & tablet (regardless its screen orientation)/ + */ +@media only screen and (min-device-width : 375px) and (max-device-width : 1024px) { + .nav-docs-section ul { + display: flex; + flex-wrap: wrap; + } + + .nav-docs-section li { + width: 100%; + } +} + +/* 2 columns layout */ +@media + /*Phone, landscape screen orientation*/ + only screen and (min-device-width : 375px) and (max-device-width : 1024px) and (orientation : landscape), + /*Tablet, portrait screen orientation*/ + only screen and (min-device-width : 768px) and (max-device-width : 1024px) and (orientation : portrait) { + .nav-docs-section li { + width: 50%; + } +} + +/* 3 columns layout on tablet (landscape screen orientation) */ +@media only screen and (min-device-width : 768px) and (max-device-width : 1024px) and (orientation : landscape) { + .nav-docs-section li { + width: 33%; + } +} + +.nav-blog li { + margin-bottom: 5px; +} + +.home-section { + margin: 50px 0; +} + +.home-section ol { + margin-left: 0; +} + +.home-divider { + border-top-color: #bbb; + margin: 0 auto; + width: 400px; +} + +.marketing-row { + *zoom: 1; + margin: 50px 0; +} + +.marketing-row:before, .marketing-row:after { + content: " "; + display: table; +} + +.marketing-row:after { + clear: both; +} + +.marketing-col { + float: left; + margin-left: 40px; + width: 280px; +} + +.marketing-col h3 { + color: #2d2d2d; + font-size: 24px; + font-weight: normal; + text-transform: uppercase; +} + +.marketing-col p { + font-size: 16px; +} + +.marketing-col:first-child { + margin-left: 0; +} + +.tutorial-mock { + text-align: center; +} +.tutorial-mock img { + border: 1px solid #ccc; + box-shadow: 5px 5px 5px #888888; +} + +#examples h3, .home-presentation h3 { + color: #2d2d2d; + font-size: 24px; + font-weight: normal; + margin-bottom: 5px; +} + +#examples p { + margin: 0 0 25px 0; + max-width: 600px; +} + +#examples .example { + margin-top: 60px; +} + +#examples #todoExample { + font-size: 14px; +} + +#examples #todoExample ul { + list-style-type: square; + margin: 0 0 10px 0; +} + +#examples #todoExample input { + border: 1px solid #ccc; + font: 14px proxima-nova, "Helvetica Neue", Helvetica, Arial, sans-serif; + padding: 3px; + width: 150px; +} + +#examples #todoExample button { + font: 14px proxima-nova, "Helvetica Neue", Helvetica, Arial, sans-serif; + margin-left: 5px; + padding: 4px 10px; +} + +#examples #markdownExample textarea { + border: 1px solid #ccc; + font: 14px proxima-nova, "Helvetica Neue", Helvetica, Arial, sans-serif; + margin-bottom: 10px; + padding: 5px; +} + +.home-bottom-section { + margin-bottom: 100px; +} + +.docs-nextprev { + *zoom: 1; +} + +.docs-nextprev:before, .docs-nextprev:after { + content: " "; + display: table; +} + +.docs-nextprev:after { + clear: both; +} + +.docs-prev { + float: left; +} + +.docs-next { + float: right; +} + +section.black content { + padding-bottom: 18px; +} + +.blogContent { + *zoom: 1; + padding-top: 20px; +} + +.blogContent:before, .blogContent:after { + content: " "; + display: table; +} + +.blogContent:after { + clear: both; +} + +.blogContent blockquote { + padding: 5px 15px; + margin: 20px 0; + background-color: #f8f5ec; + border-left: 5px solid #f7ebc6; +} + +.documentationContent { + *zoom: 1; + padding-top: 20px; + padding-bottom: 80px; +} + +.documentationContent:before, .documentationContent:after { + content: " "; + display: table; +} + +.documentationContent:after { + clear: both; +} + +.documentationContent .subHeader { + font-size: 24px; +} + +h2 { + margin-top: 30px; +} + +.documentationContent blockquote { + padding: 15px 30px 15px 15px; + margin: 20px 0; + background-color: rgba(248, 245, 236, 0.1); + border-left: 5px solid rgba(191, 87, 73, 0.2); +} + +.documentationContent blockquote h4 { + margin-top: 0; +} + +.documentationContent blockquote p { + margin-bottom: 0; +} + +.documentationContent blockquote p:first-child { + font-size: 14px; + line-height: 20px; + margin-top: 0; + text-rendering: optimizelegibility; +} + +.docs-prevnext { + padding-top: 20px; + padding-bottom: 20px; +} + +.button { + background: -webkit-linear-gradient( #9a9a9a, #646464); + background: linear-gradient( #9a9a9a, #646464); + border-radius: 4px; + padding: 8px 16px; + font-size: 18px; + font-weight: 400; + margin: 0 12px; + display: inline-block; + color: #fafafa; + text-decoration: none; + text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); + text-decoration: none; +} + +.button:hover { + text-decoration: none; +} + +.button:active { + box-shadow: none; +} + +.hero .button { + box-shadow: 1px 3px 3px rgba(0, 0, 0, 0.3); +} + +.button.blue { + background: -webkit-linear-gradient( #77a3d2, #4783c2); + background: linear-gradient( #77a3d2, #4783c2); +} + +.row { + padding-bottom: 4px; +} + +.row .span4 { + width: 33.33%; + display: table-cell; +} + +.row .span8 { + width: 66.66%; + display: table-cell; +} + +.row .span6 { + width: 50%; + display: table-cell; +} + +p { + margin: 10px 0; +} + +.highlight { + padding: 10px; + margin-bottom: 20px; +} + +figure { + text-align: center; +} + +.inner-content { + float: left; + width: 650px; +} + +.showcaseSection .inner-content { + width: 800px; +} + +.helpSection .inner-content { + width: 800px; +} + +.nosidebar .inner-content { + float: none; + margin: 0 auto; +} + +.post-list-item+.post-list-item { + margin-top: 60px; +} + +small code, li code, p code { + color: #555; + background-color: rgba(0, 0, 0, 0.04); + padding: 1px 3px; +} + +.playground { + *zoom: 1; +} + +.playground:before, .playground:after { + content: " "; + display: table; +} + +.playground:after { + clear: both; +} + +.playground-tab { + border-bottom: none !important; + border-radius: 3px 3px 0 0; + padding: 6px 8px; + font-size: 12px; + font-weight: bold; + color: #c2c0bc; + background-color: #f1ede4; + display: inline-block; + cursor: pointer; +} + +.playgroundCode, .playground-tab, .playgroundPreview { + border: 1px solid rgba(16, 16, 16, 0.1); +} + +.playground-tab-active { + color: #222; +} + +.playgroundCode { + border-radius: 0 3px 3px 3px; + float: left; + overflow: hidden; + width: 600px; +} + +.playgroundPreview { + background-color: white; + border-radius: 3px; + float: right; + padding: 15px 20px; + width: 280px; +} + +.playgroundError { + color: #c5695c; + font-size: 15px; +} + +.MarkdownEditor textarea { + width: 100%; + height: 100px; +} + +.hll { + background-color: #f7ebc6; + border-left: 5px solid #f7d87c; + display: block; + margin-left: -14px; + margin-right: -14px; + padding-left: 9px; +} + +.highlight .javascript .err { + background-color: transparent; + color: inherit; +} + +.highlight { + position: relative; + margin-bottom: 14px; + padding: 30px 14px 14px; + border: none; + border-radius: 0; + overflow: auto; +} + +.highlight pre { + padding: 0; + margin-top: 0; + margin-bottom: 0; + background-color: transparent; + border: 0; +} + +.highlight pre code { + background: none; + font-size: inherit; + padding: 0; +} + +.highlight pre .lineno { + display: inline-block; + width: 22px; + padding-right: 5px; + margin-right: 10px; + color: #bebec5; + text-align: right; +} + +.highlight:after { + position: absolute; + top: 0; + right: 0; + left: 0; + padding: 3px 7px; + font-size: 12px; + font-weight: bold; + color: #c2c0bc; + background-color: #f1ede4; + content: "Code"; +} + +.downloadCenter { + text-align: center; + margin-top: 20px; + margin-bottom: 25px; +} + +.downloadSection:hover { + text-decoration: none !important; +} + +/* Modal */ +.modal-backdrop { + background: rgba(0,0,0,.4); + display: none; + height: 100%; + left: 0; + overflow: auto; + position: fixed; + top: 0; + width: 100%; + z-index: 9900; +} + +.modal { + background: #F6F6F6; + bottom: 0; + box-shadow: 2px 2px 4px 0 rgba(0,0,0,.11); + display: none; + border-radius: 10px; + height: 95%; + left: 0; + margin: auto; + max-height: 648px; + max-width: 460px; + overflow: auto; + position: fixed; + right: 0; + top: 0; + width: 80%; + z-index: 9999; +} + +.modal-open { display: block; } + +.modal-content { + padding: 40px 24px 8px 24px; + position: relative; +} + +.modal-content iframe { margin: 0 auto; } + +.modal-button-open { + cursor: pointer; + text-align: center; +} + +.modal-button-open-img { + height: 358px; +} + +.modal-button-open-img:hover img { opacity: 0.9; } + +.modal-button-close { + background: transparent; + border-radius: 0 0 0 4px; + border: 0; + color: #555; + font-size: 1.2em; + font-weight: bolder; + line-height: 32px; + margin: 0; + padding: 0 12px; + position: absolute; + right: 0; + top: 0; +} + +.modal-button-close:active, +.modal-button-close:focus, +.modal-button-close:hover { + background: #EAF8FD; + outline: none; +} + +@media screen and (max-width: 680px) { + .container { + padding-top: 100px; + } + + .nav-docs { + padding-top: 103px; + } +} + +.post { + margin-bottom: 30px; +} + +.pagination { + margin-bottom: 30px; + width: 100%; + overflow: hidden; +} + +.pagination .next { + float: right; +} + +div[data-twttr-id] iframe { + margin: 10px auto !important; +} + +.three-column { + *zoom: 1; +} + +.three-column:before, .three-column:after { + content: " "; + display: table; +} + +.three-column:after { + clear: both; +} + +.three-column>ul { + float: left; + margin-left: 30px; + width: 190px; +} + +.three-column > ul:first-child { + margin-left: 20px; +} + +.home-why { + margin-top: 25px; +} + +.home-why h3 { + text-align: center; +} + +.home-why .blurb { + margin-bottom: 20px; + text-align: center; +} + +.home-why .list { + margin: 0 auto; + max-width: 460px; +} + +.home-getting-started { + width: 500px; + margin: 20px auto 40px auto; +} + +.home-getting-started h3 { + text-align: center; +} + + +.props { + background-color: hsl(198, 100%, 96%); +} + +.compactProps { + border-left: 2px solid hsl(198, 100%, 94%); + margin-left: 20px; + padding-left: 5px; +} + +.props > .prop:nth-child(2n) { + background-color: hsl(198, 100%, 94%); +} + +.propTitle { + font-weight: bold; + font-size: 16px; +} + +.compactProps .propTitle { + font-size: 14px; + margin-bottom: 0; + margin-top: 0; +} + +.compactProps .propTitle div { + font-weight: normal; + margin-left: 20px; +} + +.methodTitle { + font-weight: bold; + font-size: 24px; + color: #2F9C0A; +} + +.compactProps .methodTitle { + font-size: 14px; + margin-bottom: 0; + margin-top: 0; +} + +.compactProps .methodTitle div { + font-weight: normal; + margin-left: 20px; +} + +.prop { + padding: 5px 10px; +} + +.compactProps .prop { + padding: 3px 10px; +} + +.propType { + font-weight: normal; + font-size: 15px; + white-space: pre; +} + +.compactProps .propType { + font-weight: normal; + font-size: 13px; +} + +.methodType { + font-weight: normal; + font-size: 24px; +} + +.compactProps .methodType { + font-weight: normal; + font-size: 13px; +} + +.platform { + background-color: hsl(198, 100%, 87%); + border-radius: 5px; + margin-right: 5px; + padding: 0 5px; + font-size: 13px; + font-weight: normal; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +.color { + display: inline-block; + width: 20px; + height: 20px; + margin-right: 5px; + position: relative; + top: 5px; +} + +.color::before { + content: ''; + display: block; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + border: 1px solid rgba(0, 0, 0, 0.2); +} + +.deprecated { + margin-bottom: 24px; +} + +.deprecatedTitle { + margin-bottom: 6px; + line-height: 18px; + font-weight: bold; + color: #ffa500; +} + +.deprecatedIcon { + width: 18px; + height: 18px; + margin-right: 8px; + vertical-align: top; +} + +.deprecatedMessage { + margin-left: 26px; +} + +#content { + display: none; +} + + +/** Showcase **/ + +.home-showcase-section { + max-width: 800px; + margin: 20px auto 40px auto; + text-align: center; +} + +.home-showcase-section p { + max-width: 540px; + margin: 0 auto; +} + +.footnote { + font-size: 12px; + color: rgba(0, 0, 0, 0.4); +} + +.home-showcase-section .showcase img { + width: 110px; + border-radius: 20px; +} + +.showcaseHeader { + padding-bottom: 15px; + padding-top: 15px; + text-align: center; +} + +.showcase { + margin: 30px auto 30px auto; + width: 100%; + display: inline-block; + text-align: center; + vertical-align: top; +} + +@media only screen + and (min-device-width: 768px) + and (max-device-width: 1024px) { + .showcase { + width: 50%; + } +} + +@media only screen + and (min-device-width: 1024px) { + .showcase { + width: 25%; + } +} + +.showcase h3 { + margin-bottom: 0px; + line-height: 20px; + padding-left: 5px; + padding-right: 5px; + font-size: 16px; +} + +.showcase p { + margin-top: 5px; + font-weight: normal; +} + +.showcase a { + font-weight: bold; +} + +.showcase h3, .showcase p { + color: rgb(72, 72, 72); +} + +.showcase img { + width: 100px; + border-radius: 20px; +} + +.pinned img { + width: 150px; + border-radius: 20px; +} + +table.versions { + width: 60%; +} + +.versions th { + width: 20%; +} + +.versions td, .versions th { + padding: 2px 5px; +} + +.versions tr:nth-child(2n+1) { + background-color: hsl(198, 100%, 94%); +} + +@media only screen + and (max-device-width: 1024px) { + #content { + display: inline; + } + + .container { + min-width: 0; + overflow: auto; + } + .wrap { + width: auto; + } + .home-getting-started { + width: auto; + } + .inner-content { + width: auto; + float: none; + } + .marketing-col { + margin-left: 0; + float: none; + margin-bottom: 30px; + text-align: center; + } + .home-section, .marketing-row { + margin: 0; + } + .nav-main .nav-site a { + padding: 0 8px; + } + .nav-main .nav-home { + margin-left: 8px; + } + .nav-main .wrap { + padding: 0; + } + .home-divider { + display: none; + } + .hero { + padding: 10px 0 30px 0; + } + .prism { + padding: 4px 8px; + margin-left: -12px; + font-size: 11px; + } + .nav-docs .nav-docs-section { + border: none; + padding: 0; + } + h1 { + font-size: 30px; + line-height: 30px; + } + ol { + margin: 0; + } +} + + +@media only screen and (max-device-width: 840px) { + .showcaseSection .inner-content { + width: 100%; + } + + .helpSection .inner-content { + width: 100%; + } +} + +/** Algolia Doc Search **/ + + +div.algolia-search-wrapper { + display: inline-block; + vertical-align: top; + margin-left: 15px; +} + +.algolia-autocomplete .aa-dropdown-menu { + margin-left: -210px; + margin-top: -4px; +} + +@media screen and (max-width: 960px) { + div.algolia-search-wrapper { + display: none; + } +} + +input#algolia-doc-search { + background: transparent url('../img/search.png') no-repeat 10px center; + background-size: 16px 16px; + + padding: 0 10px; + padding-left: 35px; + margin-top: 10px; + height: 30px; + font-size: 16px; + line-height: 20px; + background-color: #555; + border-radius: 4px; + color: white; + outline: none; + border: none; + width: 170px; + transition: .5s width ease; + -webkit-transition: .5s width ease; + -moz-transition: .5s width ease; + -o-transition: .5s width ease; +} + +input#algolia-doc-search:focus { + width: 220px; +} + +.algolia-autocomplete { + vertical-align: top; + height: 53px; +} + +.algolia-docsearch-suggestion--category-header { + background-color: #3B3738; +} +.algolia-docsearch-suggestion--highlight { + color: #05A5D1; +} +.algolia-docsearch-suggestion--category-header .algolia-docsearch-suggestion--highlight { + background-color: #05A5D1; +} +.aa-cursor .algolia-docsearch-suggestion--content { + color: #05A5D1; +} +.aa-cursor .algolia-docsearch-suggestion { + background: hsl(198, 100%, 96%); +} +.algolia-docsearch-suggestion { + border-bottom-color: hsl(198, 100%, 94%); +} +.algolia-docsearch-suggestion--subcategory-column { + border-right-color: hsl(198, 100%, 94%); + background-color: hsl(198, 100%, 96%); + color: #3B3738; +} + +.params, .props { + border-spacing: 0; + border: 0; + border-collapse: collapse; +} + +.params .name, .props .name, .name code { + color: #4D4E53; +} + +.params td, .params th, .props td, .props th { + border: 1px solid #ddd; + margin: 0px; + text-align: left; + vertical-align: top; + padding: 4px 6px; + display: table-cell; +} + +.params thead tr, .props thead tr { + background-color: hsl(198, 75%, 88%); + font-weight: bold; +} + +.params .params thead tr, .props .props thead tr { + background-color: #fff; + font-weight: bold; +} + +.params th, .props th { border-right: 1px solid #aaa; } +.params thead .last, .props thead .last { border-right: 1px solid #ddd; } + +.params td.description > div > p:first-child, +.props td.description > div > p:first-child { + margin-top: 0; + padding-top: 0; +} + +.params td.description > p:last-child, +.props td.description > p:last-child { + margin-bottom: 0; + padding-bottom: 0; +} + +.edit-page-block { + padding: 5px; + margin-bottom: 40px; + font-size: 12px; + color: #887766; + text-align: center; + background-color: rgba(5, 165, 209, 0.05); +} + +/** Web player **/ + +.web-player > iframe, .web-player > .prism { + display: none; +} + +.web-player.desktop > iframe { + display: block; +} + +.web-player.mobile > .prism { + display: block; +} + +/** Blog **/ + +.entry-header { + margin: 40px 0 0 0; +} + +.entry-header h1 { + margin: 0; + font-size: 33px; + line-height: 36px; +} + +.entry-header h4 { + margin: 0 0 10px; + font-size: 12px; + line-height: 16px; +} + +.entry-header .author { + color: #5A6b77; +} + +.entry-header .date { + color: #66637A; +} + +.entry-readmore { + margin: 12px 0 0; +} + +.entry-share { + padding: 36px 0; + display: block; + text-align: left; +} + +.small-title { + font-size: 10px; + color: #66637A; + letter-spacing: .4rem; + text-transform: uppercase; + font-weight: 400; + line-height: 12px; +} + +.entry-share .small-title { + float: left; + width: 50%; +} + +.social-buttons { + padding-top: 7px; + float: left; + width: 50%; +} + +article { + margin: 0 0 40px 0; + line-height: 1.7; +} + +article h2 { + font-size: 26px; + line-height: 30px; +} + +article li { + line-height: 28px; +} + +.author-info { + margin-top: 26px; + text-align: center; + border-bottom: 1px solid #f1f1f1; + padding-bottom: 20px +} + +.the-image { + position: relative; + display: block; + width: 64px; + height: 64px; + margin: 0 auto; + border-radius: 50%; + background-position: center center; + background-color: #fff; + background-size: cover; +} + +.author-image { + position: relative; +} + +.author-image:before { + content: ""; + display: block; + position: absolute; + width: 100%; + height: 1px; + top: 50%; + left: 0; + background-color: #F1F1F1; +} + +.posted-on { + font-size: 12px; + color: rgba(102,99,122,.29); + margin-bottom: 0; + margin-top: 15px; +} + +.name-title { + margin-top: 2px; + font-size: 22px; + font-weight: 400; + margin: 3px 0 5px; + color: #5A6B77; +} + +.name-title a { + color: #5A6B77; +} + +.name-title .title { + color: rgba(102,99,122,.44); +} + +.btn { + background: 0 0; + color: #05A5D1; + min-width: 0; + border: 1px solid #05A5D1; + display: inline-block; + padding: 9px 18px; + border-radius: 4px; + text-align: center; + font-size: 12px; +} + +.btn a { + text-decoration: none !important; +} + +.btn:hover { + text-decoration: none !important; +} + +.video-container { + border-radius: 4px; + background-clip: padding-box; + margin: 0 0 18px; + height: 180px; + width: 100%; + background-size: cover; + background-position: center center; + position: relative; + height: 0; + overflow: hidden; +} + +@media (min-width: 760px) { + .video-container { + height: 345px; + } +} + +/** Help **/ +.helpSection h2 { + font-size: 24px; +} + +.help-row { + margin: 50px 0; +} + +.help-row:after { + content: ""; + display: table; + clear: both; +} + +.help-col { + float: left; +} + +.help-col p { + font-size: 16px; +} + +.help-col h3 { + color: #2d2d2d; + font-size: 18px; + line-height: 28px; + font-weight: normal; +} + +@media (min-width: 600px) { + .help-col { + float: left; + margin-left: 40px; + width: 240px; + } + + .help-col:first-child { + margin-left: 0; + } +} + +.help-list { + padding: 0; + list-style: none; + margin: 1.25em 0 1em 0; +} + +.entry ul, li { + margin: 0; +} + +.help-list .help-list-entry { + padding: 16px 0; + border-top: 1px solid #f1eff0; +} + +/** Footer **/ + +footer.nav-footer { + box-sizing: border-box; + border: none; + font-weight: 300; + color: #202020; + font-size: 15px; + line-height: 24px; + background: #2D2D2D; + box-shadow: inset 0 10px 10px -5px #0d1116; + padding-top: 2em; + padding-bottom: 2em; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +footer .sitemap { + display: flex; + justify-content: space-between; + max-width: 1080px; + margin: 0 auto 3em; +} +footer .sitemap div { + flex: 1; +} +footer .sitemap .nav-home { + display: table; + margin: -12px 20px 0 0; + padding: 10px; + width: 50px; + height: 50px; + opacity: 0.4; + transition: opacity 0.15s ease-in-out; +} +footer .sitemap .nav-home:hover, +footer .sitemap .nav-home:focus { + opacity: 1.0; +} +@media screen and (max-width: 740px) { + footer .sitemap { + display: none; + } +} + +footer .sitemap a { + color: white; + display: table; + margin: 2px -10px; + padding: 3px 10px; +} +footer .sitemap a:hover, +footer .sitemap a:focus { + color: #05A5D1; + text-decoration: none; +} +footer .sitemap h5 > a:hover, +footer .sitemap h5 > a:focus { + color: white; + text-decoration: none; +} +footer .sitemap h5, +footer .sitemap h6 { + margin: 0 0 10px; +} +footer .sitemap h5, +footer .sitemap h6, +footer .sitemap h5 > a, +footer .sitemap h6 > a { + color: #05A5D1; +} +footer .sitemap h5 > a, +footer .sitemap h6 > a { + margin: 0 -10px; +} +footer .fbOpenSource { + display: block; + margin: 1em auto; + opacity: 0.4; + transition: opacity 0.15s ease-in-out; + width: 170px; +} +footer .fbOpenSource:hover { + opacity: 1.0; +} +footer .copyright { + color: rgba(255, 255, 255, 0.4); + text-align: center; +} diff --git a/releases/0.37/docs/accessibility.html b/releases/0.37/docs/accessibility.html new file mode 100644 index 00000000000..03aa0fc42a4 --- /dev/null +++ b/releases/0.37/docs/accessibility.html @@ -0,0 +1,59 @@ +Accessibility

Accessibility #

Native App Accessibility (iOS and Android) #

Both iOS and Android provide APIs for making apps accessible to people with disabilities. In addition, both platforms provide bundled assistive technologies, like the screen readers VoiceOver (iOS) and TalkBack (Android) for the visually impaired. Similarly, in React Native we have included APIs designed to provide developers with support for making apps more accessible. Take note, iOS and Android differ slightly in their approaches, and thus the React Native implementations may vary by platform.

Making Apps Accessible #

Accessibility properties #

accessible (iOS, Android) #

When true, indicates that the view is an accessibility element. When a view is an accessibility element, it groups its children into a single selectable component. By default, all touchable elements are accessible.

On Android, ‘accessible={true}’ property for a react-native View will be translated into native ‘focusable={true}’.

<View accessible={true}> + <Text>text one</Text> + <Text>text two</Text> +</View>

In the above example, we can't get accessibility focus separately on 'text one' and 'text two'. Instead we get focus on a parent view with 'accessible' property.

accessibilityLabel (iOS, Android) #

When a view is marked as accessible, it is a good practice to set an accessibilityLabel on the view, so that people who use VoiceOver know what element they have selected. VoiceOver will read this string when a user selects the associated element.

To use, set the accessibilityLabel property to a custom string on your View:

<TouchableOpacity accessible={true} accessibilityLabel={'Tap me!'} onPress={this._onPress}> + <View style={styles.button}> + <Text style={styles.buttonText}>Press me!</Text> + </View> +</TouchableOpacity>

In the above example, the accessibilityLabel on the TouchableOpacity element would default to "Press me!". The label is constructed by concatenating all Text node children separated by spaces.

accessibilityTraits (iOS) #

Accessibility traits tell a person using VoiceOver what kind of element they have selected. Is this element a label? A button? A header? These questions are answered by accessibilityTraits.

To use, set the accessibilityTraits property to one of (or an array of) accessibility trait strings:

  • none Used when the element has no traits.
  • button Used when the element should be treated as a button.
  • link Used when the element should be treated as a link.
  • header Used when an element acts as a header for a content section (e.g. the title of a navigation bar).
  • search Used when the text field element should also be treated as a search field.
  • image Used when the element should be treated as an image. Can be combined with button or link, for example.
  • selected Used when the element is selected. For example, a selected row in a table or a selected button within a segmented control.
  • plays Used when the element plays its own sound when activated.
  • key Used when the element acts as a keyboard key.
  • text Used when the element should be treated as static text that cannot change.
  • summary Used when an element can be used to provide a quick summary of current conditions in the app when the app first launches. For example, when Weather first launches, the element with today's weather conditions is marked with this trait.
  • disabled Used when the control is not enabled and does not respond to user input.
  • frequentUpdates Used when the element frequently updates its label or value, but too often to send notifications. Allows an accessibility client to poll for changes. A stopwatch would be an example.
  • startsMedia Used when activating an element starts a media session (e.g. playing a movie, recording audio) that should not be interrupted by output from an assistive technology, like VoiceOver.
  • adjustable Used when an element can be "adjusted" (e.g. a slider).
  • allowsDirectInteraction Used when an element allows direct touch interaction for VoiceOver users (for example, a view representing a piano keyboard).
  • pageTurn Informs VoiceOver that it should scroll to the next page when it finishes reading the contents of the element.

onAccessibilityTap (iOS) #

Use this property to assign a custom function to be called when someone activates an accessible element by double tapping on it while it's selected.

onMagicTap (iOS) #

Assign this property to a custom function which will be called when someone performs the "magic tap" gesture, which is a double-tap with two fingers. A magic tap function should perform the most relevant action a user could take on a component. In the Phone app on iPhone, a magic tap answers a phone call, or ends the current one. If the selected element does not have an onMagicTap function, the system will traverse up the view hierarchy until it finds a view that does.

accessibilityComponentType (Android) #

In some cases, we also want to alert the end user of the type of selected component (i.e., that it is a “button”). If we were using native buttons, this would work automatically. Since we are using javascript, we need to provide a bit more context for TalkBack. To do so, you must specify the ‘accessibilityComponentType’ property for any UI component. For instances, we support ‘button’, ‘radiobutton_checked’ and ‘radiobutton_unchecked’ and so on.

<TouchableWithoutFeedback accessibilityComponentType=”button” + onPress={this._onPress}> + <View style={styles.button}> + <Text style={styles.buttonText}>Press me!</Text> + </View> +</TouchableWithoutFeedback>

In the above example, the TouchableWithoutFeedback is being announced by TalkBack as a native Button.

accessibilityLiveRegion (Android) #

When components dynamically change, we want TalkBack to alert the end user. This is made possible by the ‘accessibilityLiveRegion’ property. It can be set to ‘none’, ‘polite’ and ‘assertive’:

  • none Accessibility services should not announce changes to this view.
  • polite Accessibility services should announce changes to this view.
  • assertive Accessibility services should interrupt ongoing speech to immediately announce changes to this view.
<TouchableWithoutFeedback onPress={this._addOne}> + <View style={styles.embedded}> + <Text>Click me</Text> + </View> +</TouchableWithoutFeedback> +<Text accessibilityLiveRegion="polite"> + Clicked {this.state.count} times +</Text>

In the above example method _addOne changes the state.count variable. As soon as an end user clicks the TouchableWithoutFeedback, TalkBack reads text in the Text view because of its 'accessibilityLiveRegion=”polite”' property.

importantForAccessibility (Android) #

In the case of two overlapping UI components with the same parent, default accessibility focus can have unpredictable behavior. The ‘importantForAccessibility’ property will resolve this by controlling if a view fires accessibility events and if it is reported to accessibility services. It can be set to ‘auto’, ‘yes’, ‘no’ and ‘no-hide-descendants’ (the last value will force accessibility services to ignore the component and all of its children).

<View style={styles.container}> + <View style={{position: 'absolute', left: 10, top: 10, right: 10, height: 100, + backgroundColor: 'green'}} importantForAccessibility=”yes”> + <Text> First layout </Text> + </View> + <View style={{position: 'absolute', left: 10, top: 10, right: 10, height: 100, + backgroundColor: 'yellow'}} importantForAccessibility=”no-hide-descendant”> + <Text> Second layout </Text> + </View> +</View>

In the above example, the yellow layout and its descendants are completely invisible to TalkBack and all other accessibility services. So we can easily use overlapping views with the same parent without confusing TalkBack.

Sending Accessibility Events (Android) #

Sometimes it is useful to trigger an accessibility event on a UI component (i.e. when a custom view appears on a screen or a custom radio button has been selected). Native UIManager module exposes a method ‘sendAccessibilityEvent’ for this purpose. It takes two arguments: view tag and a type of an event.

_onPress: function() { + this.state.radioButton = this.state.radioButton === “radiobutton_checked” ? + “radiobutton_unchecked” : “radiobutton_checked”; + if (this.state.radioButton === “radiobutton_checked”) { + RCTUIManager.sendAccessibilityEvent( + ReactNative.findNodeHandle(this), + RCTUIManager.AccessibilityEventTypes.typeViewClicked); + } +} + +<CustomRadioButton + accessibleComponentType={this.state.radioButton} + onPress={this._onPress}/>

In the above example we've created a custom radio button that now behaves like a native one. More specifically, TalkBack now correctly announces changes to the radio button selection.

Testing VoiceOver Support (iOS) #

To enable VoiceOver, go to the Settings app on your iOS device. Tap General, then Accessibility. There you will find many tools that people use to use to make their devices more usable, such as bolder text, increased contrast, and VoiceOver.

To enable VoiceOver, tap on VoiceOver under "Vision" and toggle the switch that appears at the top.

At the very bottom of the Accessibility settings, there is an "Accessibility Shortcut". You can use this to toggle VoiceOver by triple clicking the Home button.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/actionsheetios.html b/releases/0.37/docs/actionsheetios.html new file mode 100644 index 00000000000..b621e7285ad --- /dev/null +++ b/releases/0.37/docs/actionsheetios.html @@ -0,0 +1,225 @@ +ActionSheetIOS

ActionSheetIOS #

Methods #

static showActionSheetWithOptions(options, callback) #

Display an iOS action sheet. The options object must contain one or more +of:

  • options (array of strings) - a list of button titles (required)
  • cancelButtonIndex (int) - index of cancel button in options
  • destructiveButtonIndex (int) - index of destructive button in options
  • title (string) - a title to show above the action sheet
  • message (string) - a message to show below the title

static showShareActionSheetWithOptions(options, failureCallback, successCallback) #

Display the iOS share sheet. The options object should contain +one or both of message and url and can additionally have +a subject or excludedActivityTypes:

  • url (string) - a URL to share
  • message (string) - a message to share
  • subject (string) - a subject for the message
  • excludedActivityTypes (array) - the activities to exclude from the ActionSheet

NOTE: if url points to a local file, or is a base64-encoded +uri, the file it points to will be loaded and shared directly. +In this way, you can share images, videos, PDF files, etc.

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + ActionSheetIOS, + StyleSheet, + Text, + UIManager, + View, +} = ReactNative; + +var BUTTONS = [ + 'Option 0', + 'Option 1', + 'Option 2', + 'Delete', + 'Cancel', +]; +var DESTRUCTIVE_INDEX = 3; +var CANCEL_INDEX = 4; + +class ActionSheetExample extends React.Component { + state = { + clicked: 'none', + }; + + render() { + return ( + <View> + <Text onPress={this.showActionSheet} style={style.button}> + Click to show the ActionSheet + </Text> + <Text> + Clicked button: {this.state.clicked} + </Text> + </View> + ); + } + + showActionSheet = () => { + ActionSheetIOS.showActionSheetWithOptions({ + options: BUTTONS, + cancelButtonIndex: CANCEL_INDEX, + destructiveButtonIndex: DESTRUCTIVE_INDEX, + }, + (buttonIndex) => { + this.setState({ clicked: BUTTONS[buttonIndex] }); + }); + }; +} + +class ActionSheetTintExample extends React.Component { + state = { + clicked: 'none', + }; + + render() { + return ( + <View> + <Text onPress={this.showActionSheet} style={style.button}> + Click to show the ActionSheet + </Text> + <Text> + Clicked button: {this.state.clicked} + </Text> + </View> + ); + } + + showActionSheet = () => { + ActionSheetIOS.showActionSheetWithOptions({ + options: BUTTONS, + cancelButtonIndex: CANCEL_INDEX, + destructiveButtonIndex: DESTRUCTIVE_INDEX, + tintColor: 'green', + }, + (buttonIndex) => { + this.setState({ clicked: BUTTONS[buttonIndex] }); + }); + }; +} + +class ShareActionSheetExample extends React.Component { + state = { + text: '' + }; + + render() { + return ( + <View> + <Text onPress={this.showShareActionSheet} style={style.button}> + Click to show the Share ActionSheet + </Text> + <Text> + {this.state.text} + </Text> + </View> + ); + } + + showShareActionSheet = () => { + ActionSheetIOS.showShareActionSheetWithOptions({ + url: this.props.url, + message: 'message to go with the shared url', + subject: 'a subject to go in the email heading', + excludedActivityTypes: [ + 'com.apple.UIKit.activity.PostToTwitter' + ] + }, + (error) => alert(error), + (success, method) => { + var text; + if (success) { + text = `Shared via ${method}`; + } else { + text = 'You didn\'t share'; + } + this.setState({text}); + }); + }; +} + +class ShareScreenshotExample extends React.Component { + state = { + text: '' + }; + + render() { + return ( + <View> + <Text onPress={this.showShareActionSheet} style={style.button}> + Click to show the Share ActionSheet + </Text> + <Text> + {this.state.text} + </Text> + </View> + ); + } + + showShareActionSheet = () => { + // Take the snapshot (returns a temp file uri) + UIManager.takeSnapshot('window').then((uri) => { + // Share image data + ActionSheetIOS.showShareActionSheetWithOptions({ + url: uri, + excludedActivityTypes: [ + 'com.apple.UIKit.activity.PostToTwitter' + ] + }, + (error) => alert(error), + (success, method) => { + var text; + if (success) { + text = `Shared via ${method}`; + } else { + text = 'You didn\'t share'; + } + this.setState({text}); + }); + }).catch((error) => alert(error)); + }; +} + +var style = StyleSheet.create({ + button: { + marginBottom: 10, + fontWeight: '500', + } +}); + +exports.title = 'ActionSheetIOS'; +exports.description = 'Interface to show iOS\' action sheets'; +exports.examples = [ + { + title: 'Show Action Sheet', + render(): React.Element<any> { return <ActionSheetExample />; } + }, + { + title: 'Show Action Sheet with tinted buttons', + render(): React.Element<any> { return <ActionSheetTintExample />; } + }, + { + title: 'Show Share Action Sheet', + render(): React.Element<any> { + return <ShareActionSheetExample url="https://code.facebook.com" />; + } + }, + { + title: 'Share Local Image', + render(): React.Element<any> { + return <ShareActionSheetExample url="bunny.png" />; + } + }, + { + title: 'Share Screenshot', + render(): React.Element<any> { + return <ShareScreenshotExample />; + } + } +];
\ No newline at end of file diff --git a/releases/0.37/docs/activityindicator.html b/releases/0.37/docs/activityindicator.html new file mode 100644 index 00000000000..cf3dfe2d31e --- /dev/null +++ b/releases/0.37/docs/activityindicator.html @@ -0,0 +1,201 @@ +ActivityIndicator

ActivityIndicator #

Displays a circular loading indicator.

Props #

animating bool #

Whether to show the indicator (true, the default) or hide it (false).

color color #

The foreground color of the spinner (default is gray).

size enum('small', 'large'), number #

Size of the indicator (default is 'small'). +Passing a number to the size prop is only supported on Android.

ioshidesWhenStopped bool #

Whether the indicator should hide when not animating (true by default).

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +import React, { Component } from 'react'; +import { ActivityIndicator, StyleSheet, View } from 'react-native'; + +/** + * Optional Flowtype state and timer types definition + */ +type State = { animating: boolean; }; +type Timer = number; + +class ToggleAnimatingActivityIndicator extends Component { + /** + * Optional Flowtype state and timer types + */ + state: State; + _timer: Timer; + + constructor(props) { + super(props); + this.state = { + animating: true, + }; + } + + componentDidMount() { + this.setToggleTimeout(); + } + + componentWillUnmount() { + clearTimeout(this._timer); + } + + setToggleTimeout() { + this._timer = setTimeout(() => { + this.setState({animating: !this.state.animating}); + this.setToggleTimeout(); + }, 2000); + } + + render() { + return ( + <ActivityIndicator + animating={this.state.animating} + style={[styles.centering, {height: 80}]} + size="large" + /> + ); + } +} + + + +exports.displayName = (undefined: ?string); +exports.framework = 'React'; +exports.title = '<ActivityIndicator>'; +exports.description = 'Animated loading indicators.'; + +exports.examples = [ + { + title: 'Default (small, white)', + render() { + return ( + <ActivityIndicator + style={[styles.centering, styles.gray]} + color="white" + /> + ); + } + }, + { + title: 'Gray', + render() { + return ( + <View> + <ActivityIndicator + style={[styles.centering]} + /> + <ActivityIndicator + style={[styles.centering, {backgroundColor: '#eeeeee'}]} + /> + </View> + ); + } + }, + { + title: 'Custom colors', + render() { + return ( + <View style={styles.horizontal}> + <ActivityIndicator color="#0000ff" /> + <ActivityIndicator color="#aa00aa" /> + <ActivityIndicator color="#aa3300" /> + <ActivityIndicator color="#00aa00" /> + </View> + ); + } + }, + { + title: 'Large', + render() { + return ( + <ActivityIndicator + style={[styles.centering, styles.gray]} + size="large" + color="white" + /> + ); + } + }, + { + title: 'Large, custom colors', + render() { + return ( + <View style={styles.horizontal}> + <ActivityIndicator + size="large" + color="#0000ff" + /> + <ActivityIndicator + size="large" + color="#aa00aa" + /> + <ActivityIndicator + size="large" + color="#aa3300" + /> + <ActivityIndicator + size="large" + color="#00aa00" + /> + </View> + ); + } + }, + { + title: 'Start/stop', + render() { + return <ToggleAnimatingActivityIndicator />; + } + }, + { + title: 'Custom size', + render() { + return ( + <ActivityIndicator + style={[styles.centering, {transform: [{scale: 1.5}]}]} + size="large" + /> + ); + } + }, + { + platform: 'android', + title: 'Custom size (size: 75)', + render() { + return ( + <ActivityIndicator + style={styles.centering} + size={75} + /> + ); + } + }, +]; + + +const styles = StyleSheet.create({ + centering: { + alignItems: 'center', + justifyContent: 'center', + padding: 8, + }, + gray: { + backgroundColor: '#cccccc', + }, + horizontal: { + flexDirection: 'row', + justifyContent: 'space-around', + padding: 8, + }, +});
\ No newline at end of file diff --git a/releases/0.37/docs/adsupportios.html b/releases/0.37/docs/adsupportios.html new file mode 100644 index 00000000000..6af15d8619b --- /dev/null +++ b/releases/0.37/docs/adsupportios.html @@ -0,0 +1,105 @@ +AdSupportIOS

AdSupportIOS #

Methods #

static getAdvertisingId(onSuccess, onFailure) #

static getAdvertisingTrackingEnabled(onSuccess, onFailure) #

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + AdSupportIOS, + StyleSheet, + Text, + View, +} = ReactNative; + +exports.framework = 'React'; +exports.title = 'Advertising ID'; +exports.description = 'Example of using the ad support API.'; + +exports.examples = [ + { + title: 'Ad Support IOS', + render: function(): React.Element<any> { + return <AdSupportIOSExample />; + }, + } +]; + +class AdSupportIOSExample extends React.Component { + state = { + deviceID: 'No IDFA yet', + hasAdvertiserTracking: 'unset', + }; + + componentDidMount() { + AdSupportIOS.getAdvertisingId( + this._onDeviceIDSuccess, + this._onDeviceIDFailure + ); + + AdSupportIOS.getAdvertisingTrackingEnabled( + this._onHasTrackingSuccess, + this._onHasTrackingFailure + ); + } + + _onHasTrackingSuccess = (hasTracking) => { + this.setState({ + 'hasAdvertiserTracking': hasTracking, + }); + }; + + _onHasTrackingFailure = (e) => { + this.setState({ + 'hasAdvertiserTracking': 'Error!', + }); + }; + + _onDeviceIDSuccess = (deviceID) => { + this.setState({ + 'deviceID': deviceID, + }); + }; + + _onDeviceIDFailure = (e) => { + this.setState({ + 'deviceID': 'Error!', + }); + }; + + render() { + return ( + <View> + <Text> + <Text style={styles.title}>Advertising ID: </Text> + {JSON.stringify(this.state.deviceID)} + </Text> + <Text> + <Text style={styles.title}>Has Advertiser Tracking: </Text> + {JSON.stringify(this.state.hasAdvertiserTracking)} + </Text> + </View> + ); + } +} + +var styles = StyleSheet.create({ + title: { + fontWeight: '500', + }, +});
\ No newline at end of file diff --git a/releases/0.37/docs/alert.html b/releases/0.37/docs/alert.html new file mode 100644 index 00000000000..785889026ca --- /dev/null +++ b/releases/0.37/docs/alert.html @@ -0,0 +1,169 @@ +Alert

Alert #

Launches an alert dialog with the specified title and message.

Optionally provide a list of buttons. Tapping any button will fire the +respective onPress callback and dismiss the alert. By default, the only +button will be an 'OK' button.

This is an API that works both on iOS and Android and can show static +alerts. To show an alert that prompts the user to enter some information, +see AlertIOS; entering text in an alert is common on iOS only.

iOS #

On iOS you can specify any number of buttons. Each button can optionally +specify a style, which is one of 'default', 'cancel' or 'destructive'.

Android #

On Android at most three buttons can be specified. Android has a concept +of a neutral, negative and a positive button:

  • If you specify one button, it will be the 'positive' one (such as 'OK')
  • Two buttons mean 'negative', 'positive' (such as 'Cancel', 'OK')
  • Three buttons mean 'neutral', 'negative', 'positive' (such as 'Later', 'Cancel', 'OK')
// Works on both iOS and Android +Alert.alert( + 'Alert Title', + 'My Alert Msg', + [ + {text: 'Ask me later', onPress: () => console.log('Ask me later pressed')}, + {text: 'Cancel', onPress: () => console.log('Cancel Pressed'), style: 'cancel'}, + {text: 'OK', onPress: () => console.log('OK Pressed')}, + ] +)

Methods #

static alert(title, message?, buttons?, options?, type?) #

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + Alert, + StyleSheet, + Text, + TouchableHighlight, + View, +} = ReactNative; + +var UIExplorerBlock = require('./UIExplorerBlock'); + +// corporate ipsum > lorem ipsum +var alertMessage = 'Credibly reintermediate next-generation potentialities after goal-oriented ' + + 'catalysts for change. Dynamically revolutionize.'; + +/** + * Simple alert examples. + */ +class SimpleAlertExampleBlock extends React.Component { + render() { + return ( + <View> + <TouchableHighlight style={styles.wrapper} + onPress={() => Alert.alert( + 'Alert Title', + alertMessage, + )}> + <View style={styles.button}> + <Text>Alert with message and default button</Text> + </View> + </TouchableHighlight> + <TouchableHighlight style={styles.wrapper} + onPress={() => Alert.alert( + 'Alert Title', + alertMessage, + [ + {text: 'OK', onPress: () => console.log('OK Pressed!')}, + ] + )}> + <View style={styles.button}> + <Text>Alert with one button</Text> + </View> + </TouchableHighlight> + <TouchableHighlight style={styles.wrapper} + onPress={() => Alert.alert( + 'Alert Title', + alertMessage, + [ + {text: 'Cancel', onPress: () => console.log('Cancel Pressed!')}, + {text: 'OK', onPress: () => console.log('OK Pressed!')}, + ] + )}> + <View style={styles.button}> + <Text>Alert with two buttons</Text> + </View> + </TouchableHighlight> + <TouchableHighlight style={styles.wrapper} + onPress={() => Alert.alert( + 'Alert Title', + null, + [ + {text: 'Foo', onPress: () => console.log('Foo Pressed!')}, + {text: 'Bar', onPress: () => console.log('Bar Pressed!')}, + {text: 'Baz', onPress: () => console.log('Baz Pressed!')}, + ] + )}> + <View style={styles.button}> + <Text>Alert with three buttons</Text> + </View> + </TouchableHighlight> + <TouchableHighlight style={styles.wrapper} + onPress={() => Alert.alert( + 'Foo Title', + alertMessage, + '..............'.split('').map((dot, index) => ({ + text: 'Button ' + index, + onPress: () => console.log('Pressed ' + index) + })) + )}> + <View style={styles.button}> + <Text>Alert with too many buttons</Text> + </View> + </TouchableHighlight> + <TouchableHighlight style={styles.wrapper} + onPress={() => Alert.alert( + 'Alert Title', + null, + [ + {text: 'OK', onPress: () => console.log('OK Pressed!')}, + ], + { + cancelable: false + } + )}> + <View style={styles.button}> + <Text>Alert that cannot be dismissed</Text> + </View> + </TouchableHighlight> + </View> + ); + } +} + +class AlertExample extends React.Component { + static title = 'Alert'; + + static description = 'Alerts display a concise and informative message ' + + 'and prompt the user to make a decision.'; + + render() { + return ( + <UIExplorerBlock title={'Alert'}> + <SimpleAlertExampleBlock /> + </UIExplorerBlock> + ); + } +} + +var styles = StyleSheet.create({ + wrapper: { + borderRadius: 5, + marginBottom: 5, + }, + button: { + backgroundColor: '#eeeeee', + padding: 10, + }, +}); + +module.exports = { + AlertExample, + SimpleAlertExampleBlock, +};
\ No newline at end of file diff --git a/releases/0.37/docs/alertios.html b/releases/0.37/docs/alertios.html new file mode 100644 index 00000000000..7d20f1aba2c --- /dev/null +++ b/releases/0.37/docs/alertios.html @@ -0,0 +1,224 @@ +AlertIOS

AlertIOS #

AlertIOS provides functionality to create an iOS alert dialog with a +message or create a prompt for user input.

Creating an iOS alert:

AlertIOS.alert( + 'Sync Complete', + 'All your data are belong to us.' +);

Creating an iOS prompt:

AlertIOS.prompt( + 'Enter a value', + null, + text => console.log("You entered "+text) +);

We recommend using the Alert.alert method for +cross-platform support if you don't need to create iOS-only prompts.

Methods #

static alert(title, message?, callbackOrButtons?, type?) #

Create and display a popup alert.

Parameters:
Name and TypeDescription
title

string

The dialog's title.

[message]

string

An optional message that appears below + the dialog's title.

[callbackOrButtons]

?(() => void) | ButtonsArray

This optional argument should + be either a single-argument function or an array of buttons. If passed + a function, it will be called when the user taps 'OK'.

If passed an array of button configurations, each button should include + a text key, as well as optional onPress and style keys. style + should be one of 'default', 'cancel' or 'destructive'.

[type]

Deprecated, do not use.


Example with custom buttons:
AlertIOS.alert( + 'Update available', + 'Keep your app up to date to enjoy the latest features', + [ + {text: 'Cancel', onPress: () => console.log('Cancel Pressed'), style: 'cancel'}, + {text: 'Install', onPress: () => console.log('Install Pressed')}, + ], +);

static prompt(title, message?, callbackOrButtons?, type?, defaultValue?) #

Create and display a prompt to enter some text.

Parameters:
Name and TypeDescription
title

string

The dialog's title.

[message]

string

An optional message that appears above the text + input.

[callbackOrButtons]

?((text: string) => void) | ButtonsArray

This optional argument should + be either a single-argument function or an array of buttons. If passed + a function, it will be called with the prompt's value when the user + taps 'OK'.

If passed an array of button configurations, each button should include + a text key, as well as optional onPress and style keys (see + example). style should be one of 'default', 'cancel' or 'destructive'.

[type]

This configures the text input. One of 'plain-text', + 'secure-text' or 'login-password'.

[defaultValue]

string

The default text in text input.


Example with custom buttons:
AlertIOS.prompt( + 'Enter password', + 'Enter your password to claim your $1.5B in lottery winnings', + [ + {text: 'Cancel', onPress: () => console.log('Cancel Pressed'), style: 'cancel'}, + {text: 'OK', onPress: password => console.log('OK Pressed, password: ' + password)}, + ], + 'secure-text' +);

Example with the default button and a custom callback:
AlertIOS.prompt( + 'Update username', + null, + text => console.log("Your username is "+text), + null, + 'default' +);

Type Definitions #

AlertType #

An Alert button type

Type:
$Enum

Constants:
ValueDescription
default

Default alert with no inputs

plain-text

Plain text input alert

secure-text

Secure text input alert

login-password

Login and password alert

AlertButtonStyle #

An Alert button style

Type:
$Enum

Constants:
ValueDescription
default

Default button style

cancel

Cancel button style

destructive

Destructive button style

ButtonsArray #

Array or buttons

Type:
Array

Properties:
Name and TypeDescription
[text]

string

Button label

[onPress]

function

Callback function when button pressed

[style]

Button style

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + StyleSheet, + View, + Text, + TouchableHighlight, + AlertIOS, +} = ReactNative; + +var { SimpleAlertExampleBlock } = require('./AlertExample'); + +exports.framework = 'React'; +exports.title = 'AlertIOS'; +exports.description = 'iOS alerts and action sheets'; +exports.examples = [{ + title: 'Alerts', + render() { + return <SimpleAlertExampleBlock />; + } +}, +{ + title: 'Prompt Options', + render(): React.Element<any> { + return <PromptOptions />; + } +}, +{ + title: 'Prompt Types', + render() { + return ( + <View> + <TouchableHighlight + style={styles.wrapper} + onPress={() => AlertIOS.prompt('Plain Text Entry')}> + + <View style={styles.button}> + <Text> + plain-text + </Text> + </View> + + </TouchableHighlight> + <TouchableHighlight + style={styles.wrapper} + onPress={() => AlertIOS.prompt('Secure Text', null, null, 'secure-text')}> + + <View style={styles.button}> + <Text> + secure-text + </Text> + </View> + + </TouchableHighlight> + <TouchableHighlight + style={styles.wrapper} + onPress={() => AlertIOS.prompt('Login & Password', null, null, 'login-password')}> + + <View style={styles.button}> + <Text> + login-password + </Text> + </View> + + </TouchableHighlight> + </View> + ); + } +}]; + +class PromptOptions extends React.Component { + state: any; + customButtons: Array<Object>; + + constructor(props) { + super(props); + + // $FlowFixMe this seems to be a Flow bug, `saveResponse` is defined below + this.saveResponse = this.saveResponse.bind(this); + + this.customButtons = [{ + text: 'Custom OK', + onPress: this.saveResponse + }, { + text: 'Custom Cancel', + style: 'cancel', + }]; + + this.state = { + promptValue: undefined, + }; + } + + render() { + return ( + <View> + <Text style={{marginBottom: 10}}> + <Text style={{fontWeight: 'bold'}}>Prompt value:</Text> {this.state.promptValue} + </Text> + + <TouchableHighlight + style={styles.wrapper} + onPress={() => AlertIOS.prompt('Type a value', null, this.saveResponse)}> + + <View style={styles.button}> + <Text> + prompt with title & callback + </Text> + </View> + </TouchableHighlight> + + <TouchableHighlight + style={styles.wrapper} + onPress={() => AlertIOS.prompt('Type a value', null, this.customButtons)}> + + <View style={styles.button}> + <Text> + prompt with title & custom buttons + </Text> + </View> + </TouchableHighlight> + + <TouchableHighlight + style={styles.wrapper} + onPress={() => AlertIOS.prompt('Type a value', null, this.saveResponse, undefined, 'Default value')}> + + <View style={styles.button}> + <Text> + prompt with title, callback & default value + </Text> + </View> + </TouchableHighlight> + + <TouchableHighlight + style={styles.wrapper} + onPress={() => AlertIOS.prompt('Type a value', null, this.customButtons, 'login-password', 'admin@site.com')}> + + <View style={styles.button}> + <Text> + prompt with title, custom buttons, login/password & default value + </Text> + </View> + </TouchableHighlight> + </View> + ); + } + + saveResponse(promptValue) { + this.setState({ promptValue: JSON.stringify(promptValue) }); + } +} + +var styles = StyleSheet.create({ + wrapper: { + borderRadius: 5, + marginBottom: 5, + }, + button: { + backgroundColor: '#eeeeee', + padding: 10, + }, +});
\ No newline at end of file diff --git a/releases/0.37/docs/android-building-from-source.html b/releases/0.37/docs/android-building-from-source.html new file mode 100644 index 00000000000..55ba1b61952 --- /dev/null +++ b/releases/0.37/docs/android-building-from-source.html @@ -0,0 +1,49 @@ +Building React Native from source

Building React Native from source #

You will need to build React Native from source if you want to work on a new feature/bug fix, try out the latest features which are not released yet, or maintain your own fork with patches that cannot be merged to the core.

Prerequisites #

Assuming you have the Android SDK installed, run android to open the Android SDK Manager.

Make sure you have the following installed:

  1. Android SDK version 23 (compileSdkVersion in build.gradle)
  2. SDK build tools version 23.0.1 (buildToolsVersion in build.gradle)
  3. Local Maven repository for Support Libraries (formerly Android Support Repository) >= 17 (for Android Support Library)
  4. Android NDK (download links and installation instructions below)

Point Gradle to your Android SDK: #

Step 1: Set environment variables through your local shell.

Note: Files may vary based on shell flavor. See below for examples from common shells.

  • bash: .bash_profile or .bashrc
  • zsh: .zprofile or .zshrc
  • ksh: .profile or $ENV

Example:

export ANDROID_SDK=/Users/your_unix_name/android-sdk-macosx +export ANDROID_NDK=/Users/your_unix_name/android-ndk/android-ndk-r10e

Step 2: Create a local.properties file in the android directory of your react-native app with the following contents:

Example:

sdk.dir=/Users/your_unix_name/android-sdk-macosx +ndk.dir=/Users/your_unix_name/android-ndk/android-ndk-r10e

Download links for Android NDK #

  1. Mac OS (64-bit) - http://dl.google.com/android/repository/android-ndk-r10e-darwin-x86_64.zip
  2. Linux (64-bit) - http://dl.google.com/android/repository/android-ndk-r10e-linux-x86_64.zip
  3. Windows (64-bit) - http://dl.google.com/android/repository/android-ndk-r10e-windows-x86_64.zip
  4. Windows (32-bit) - http://dl.google.com/android/repository/android-ndk-r10e-windows-x86.zip

You can find further instructions on the official page.

Building the source #

1. Installing the fork #

First, you need to install react-native from your fork. For example, to install the master branch from the official repo, run the following:

npm install --save github:facebook/react-native#master

Alternatively, you can clone the repo to your node_modules directory and run npm install inside the cloned repo.

2. Adding gradle dependencies #

Add gradle-download-task as dependency in android/build.gradle:

... + dependencies { + classpath 'com.android.tools.build:gradle:1.3.1' + classpath 'de.undercouch:gradle-download-task:2.0.0' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +...

3. Adding the :ReactAndroid project #

Add the :ReactAndroid project in android/settings.gradle:

... +include ':ReactAndroid' + +project(':ReactAndroid').projectDir = new File( + rootProject.projectDir, '../node_modules/react-native/ReactAndroid') +...

Modify your android/app/build.gradle to use the :ReactAndroid project instead of the pre-compiled library, e.g. - replace compile 'com.facebook.react:react-native:+' with compile project(':ReactAndroid'):

... +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + compile 'com.android.support:appcompat-v7:23.0.1' + + compile project(':ReactAndroid') + + ... +} +...

4. Making 3rd-party modules use your fork #

If you use 3rd-party React Native modules, you need to override their dependencies so that they don't bundle the pre-compiled library. Otherwise you'll get an error while compiling - Error: more than one library with package name 'com.facebook.react'.

Modify your android/app/build.gradle and replace compile project(':react-native-custom-module') with:

compile(project(':react-native-custom-module')) { + exclude group: 'com.facebook.react', module: 'react-native' +}

Building from Android Studio #

From the Welcome screen of Android Studio choose "Import project" and select the android folder of your app.

You should be able to use the Run button to run your app on a device. Android Studio won't start the packager automatically, you'll need to start it by running npm start on the command line.

Additional notes #

Building from source can take a long time, especially for the first build, as it needs to download ~200 MB of artifacts and compile the native code. Every time you update the react-native version from your repo, the build directory may get deleted, and all the files are re-downloaded. To avoid this, you might want to change your build directory path by editing the ~/.gradle/init.gradle file:

gradle.projectsLoaded { + rootProject.allprojects { + buildDir = "/path/to/build/directory/${rootProject.name}/${project.name}" + } +}

Testing #

If you made changes to React Native and submit a pull request, all tests will run on your pull request automatically. To run the tests locally, see Testing.

Troubleshooting #

Gradle build fails in ndk-build. See the section about local.properties file above.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/android-ui-performance.html b/releases/0.37/docs/android-ui-performance.html new file mode 100644 index 00000000000..8f83e469c5a --- /dev/null +++ b/releases/0.37/docs/android-ui-performance.html @@ -0,0 +1,19 @@ +Profiling Android UI Performance

Profiling Android UI Performance #

We try our best to deliver buttery-smooth UI performance by default, but sometimes that just isn't possible. Remember, Android supports 10k+ different phones and is generalized to support software rendering: the framework architecture and need to generalize across many hardware targets unfortunately means you get less for free relative to iOS. But sometimes, there are things you can improve (and many times it's not native code's fault at all!).

The first step for debugging this jank is to answer the fundamental question of where your time is being spent during each 16ms frame. For that, we'll be using a standard Android profiling tool called systrace. But first...

Make sure that JS dev mode is OFF!

You should see __DEV__ === false, development-level warning are OFF, performance optimizations are ON in your application logs (which you can view using adb logcat)

Profiling with Systrace #

Systrace is a standard Android marker-based profiling tool (and is installed when you install the Android platform-tools package). Profiled code blocks are surrounded by markers start/end markers which are then visualized in a colorful chart format. Both the Android SDK and React Native framework provide standard markers that you can visualize.

Collecting a trace #

NOTE:

Systrace support was added in react-native v0.15. You will need to build with that version to collect a trace.

First, connect a device that exhibits the stuttering you want to investigate to your computer via USB and get it to the point right before the navigation/animation you want to profile. Run systrace as follows

$ <path_to_android_sdk>/platform-tools/systrace/systrace.py --time=10 -o trace.html sched gfx view -a <your_package_name>

A quick breakdown of this command:

  • time is the length of time the trace will be collected in seconds
  • sched, gfx, and view are the android SDK tags (collections of markers) we care about: sched gives you information about what's running on each core of your phone, gfx gives you graphics info such as frame boundaries, and view gives you information about measure, layout, and draw passes
  • -a <your_package_name> enables app-specific markers, specifically the ones built into the React Native framework. your_package_name can be found in the AndroidManifest.xml of your app and looks like com.example.app

Once the trace starts collecting, perform the animation or interaction you care about. At the end of the trace, systrace will give you a link to the trace which you can open in your browser.

Reading the trace #

After opening the trace in your browser (preferably Chrome), you should see something like this:

Example

If your trace .html file isn't opening correctly, check your browser console for the following:

ObjectObserveError

Since Object.observe was deprecated in recent browsers, you may have to open the file from the Google Chrome Tracing tool. You can do so by:

  • Opening tab in chrome chrome://tracing
  • Selecting load
  • Selecting the html file generated from the previous command.

HINT: Use the WASD keys to strafe and zoom

Enable VSync highlighting #

The first thing you should do is highlight the 16ms frame boundaries if you haven't already done that. Check this checkbox at the top right of the screen:

Enable VSync Highlighting

You should see zebra stripes as in the screenshot above. If you don't, try profiling on a different device: Samsung has been known to have issues displaying vsyncs while the Nexus series is generally pretty reliable.

Find your process #

Scroll until you see (part of) the name of your package. In this case, I was profiling com.facebook.adsmanager, which shows up as book.adsmanager because of silly thread name limits in the kernel.

On the left side, you'll see a set of threads which correspond to the timeline rows on the right. There are three/four threads we care about for our purposes: the UI thread (which has your package name or the name UI Thread), mqt_js and mqt_native_modules. If you're running on Android 5+, we also care about the Render Thread.

UI Thread #

This is where standard android measure/layout/draw happens. The thread name on the right will be your package name (in my case book.adsmanager) or UI Thread. The events that you see on this thread should look something like this and have to do with Choreographer, traversals, and DispatchUI:

UI Thread Example

JS Thread #

This is where JS is executed. The thread name will be either mqt_js or <...> depending on how cooperative the kernel on your device is being. To identify it if it doesn't have a name, look for things like JSCall, Bridge.executeJSCall, etc:

JS Thread Example

Native Modules Thread #

This is where native module calls (e.g. the UIManager) are executed. The thread name will be either mqt_native_modules or <...>. To identify it in the latter case, look for things like NativeCall, callJavaModuleMethod, and onBatchComplete:

Native Modules Thread Example

Bonus: Render Thread #

If you're using Android L (5.0) and up, you will also have a render thread in your application. This thread generates the actual OpenGL commands used to draw your UI. The thread name will be either RenderThread or <...>. To identify it in the latter case, look for things like DrawFrame and queueBuffer:

Render Thread Example

Identifying a culprit #

A smooth animation should look something like the following:

Smooth Animation

Each change in color is a frame -- remember that in order to display a frame, all our UI work needs to be done by the end of that 16ms period. Notice that no thread is working close to the frame boundary. An application rendering like this is rendering at 60FPS.

If you noticed chop, however, you might see something like this:

Choppy Animation from JS

Notice that the JS thread is executing basically all the time, and across frame boundaries! This app is not rendering at 60FPS. In this case, the problem lies in JS.

You might also see something like this:

Choppy Animation from UI

In this case, the UI and render threads are the ones that have work crossing frame boundaries. The UI that we're trying to render on each frame is requiring too much work to be done. In this case, the problem lies in the native views being rendered.

At this point, you'll have some very helpful information to inform your next steps.

JS Issues #

If you identified a JS problem, look for clues in the specific JS that you're executing. In the scenario above, we see RCTEventEmitter being called multiple times per frame. Here's a zoom-in of the JS thread from the trace above:

Too much JS

This doesn't seem right. Why is it being called so often? Are they actually different events? The answers to these questions will probably depend on your product code. And many times, you'll want to look into shouldComponentUpdate.

TODO: Add more tools for profiling JS

Native UI Issues #

If you identified a native UI problem, there are usually two scenarios:

  1. the UI you're trying to draw each frame involves to much work on the GPU, or
  2. You're constructing new UI during the animation/interaction (e.g. loading in new content during a scroll).

Too much GPU work #

In the first scenario, you'll see a trace that has the UI thread and/or Render Thread looking like this:

Overloaded GPU

Notice the long amount of time spent in DrawFrame that crosses frame boundaries. This is time spent waiting for the GPU to drain its command buffer from the previous frame.

To mitigate this, you should:

  • investigate using renderToHardwareTextureAndroid for complex, static content that is being animated/transformed (e.g. the Navigator slide/alpha animations)
  • make sure that you are not using needsOffscreenAlphaCompositing, which is disabled by default, as it greatly increases the per-frame load on the GPU in most cases.

If these don't help and you want to dig deeper into what the GPU is actually doing, you can check out Tracer for OpenGL ES.

Creating new views on the UI thread #

In the second scenario, you'll see something more like this:

Creating Views

Notice that first the JS thread thinks for a bit, then you see some work done on the native modules thread, followed by an expensive traversal on the UI thread.

There isn't an easy way to mitigate this unless you're able to postpone creating new UI until after the interaction, or you are able to simplify the UI you're creating. The react native team is working on a infrastructure level solution for this that will allow new UI to be created and configured off the main thread, allowing the interaction to continue smoothly.

Still stuck? #

If you are confused or stuck, please post ask on Stack Overflow with the react-native tag. If you are unable to get a response there, or find an issue with a core component, please File a Github issue.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/animated.html b/releases/0.37/docs/animated.html new file mode 100644 index 00000000000..1d5e8eedb96 --- /dev/null +++ b/releases/0.37/docs/animated.html @@ -0,0 +1,371 @@ +Animated

Animated #

Animations are an important part of modern UX, and the Animated +library is designed to make them fluid, powerful, and easy to build and +maintain.

The simplest workflow is to create an Animated.Value, hook it up to one or +more style attributes of an animated component, and then drive updates either +via animations, such as Animated.timing, or by hooking into gestures like +panning or scrolling via Animated.event. Animated.Value can also bind to +props other than style, and can be interpolated as well. Here is a basic +example of a container view that will fade in when it's mounted:

class FadeInView extends React.Component { + constructor(props) { + super(props); + this.state = { + fadeAnim: new Animated.Value(0), // init opacity 0 + }; + } + componentDidMount() { + Animated.timing( // Uses easing functions + this.state.fadeAnim, // The value to drive + {toValue: 1} // Configuration + ).start(); // Don't forget start! + } + render() { + return ( + <Animated.View // Special animatable View + style={{opacity: this.state.fadeAnim}}> // Binds + {this.props.children} + </Animated.View> + ); + } + }

Note that only animatable components can be animated. View, Text, and +Image are already provided, and you can create custom ones with +createAnimatedComponent. These special components do the magic of binding +the animated values to the properties, and do targeted native updates to +avoid the cost of the react render and reconciliation process on every frame. +They also handle cleanup on unmount so they are safe by default.

Animations are heavily configurable. Custom and pre-defined easing +functions, delays, durations, decay factors, spring constants, and more can +all be tweaked depending on the type of animation.

A single Animated.Value can drive any number of properties, and each +property can be run through an interpolation first. An interpolation maps +input ranges to output ranges, typically using a linear interpolation but +also supports easing functions. By default, it will extrapolate the curve +beyond the ranges given, but you can also have it clamp the output value.

For example, you may want to think about your Animated.Value as going from +0 to 1, but animate the position from 150px to 0px and the opacity from 0 to +1. This can easily be done by modifying style in the example above like so:

style={{ + opacity: this.state.fadeAnim, // Binds directly + transform: [{ + translateY: this.state.fadeAnim.interpolate({ + inputRange: [0, 1], + outputRange: [150, 0] // 0 : 150, 0.5 : 75, 1 : 0 + }), + }], + }}>

Animations can also be combined in complex ways using composition functions +such as sequence and parallel, and can also be chained together simply +by setting the toValue of one animation to be another Animated.Value.

Animated.ValueXY is handy for 2D animations, like panning, and there are +other helpful additions like setOffset and getLayout to aid with typical +interaction patterns, like drag-and-drop.

You can see more example usage in AnimationExample.js, the Gratuitous +Animation App, and Animations documentation guide.

Note that Animated is designed to be fully serializable so that animations +can be run in a high performance way, independent of the normal JavaScript +event loop. This does influence the API, so keep that in mind when it seems a +little trickier to do something compared to a fully synchronous system. +Checkout Animated.Value.addListener as a way to work around some of these +limitations, but use it sparingly since it might have performance +implications in the future.

Methods #

static decay(value, config) #

Animates a value from an initial velocity to zero based on a decay +coefficient.

static timing(value, config) #

Animates a value along a timed easing curve. The Easing module has tons +of pre-defined curves, or you can use your own function.

static spring(value, config) #

Spring animation based on Rebound and Origami. Tracks velocity state to +create fluid motions as the toValue updates, and can be chained together.

static add(a, b) #

Creates a new Animated value composed from two Animated values added +together.

static divide(a, b) #

Creates a new Animated value composed by dividing the first Animated value +by the second Animated value.

static multiply(a, b) #

Creates a new Animated value composed from two Animated values multiplied +together.

static modulo(a, modulus) #

Creates a new Animated value that is the (non-negative) modulo of the +provided Animated value

static diffClamp(a, min, max) #

Create a new Animated value that is limited between 2 values. It uses the +difference between the last value so even if the value is far from the bounds +it will start changing when the value starts getting closer again. +(value = clamp(value + diff, min, max)).

This is useful with scroll events, for example, to show the navbar when +scrolling up and to hide it when scrolling down.

static delay(time) #

Starts an animation after the given delay.

static sequence(animations) #

Starts an array of animations in order, waiting for each to complete +before starting the next. If the current running animation is stopped, no +following animations will be started.

static parallel(animations, config?) #

Starts an array of animations all at the same time. By default, if one +of the animations is stopped, they will all be stopped. You can override +this with the stopTogether flag.

static stagger(time, animations) #

Array of animations may run in parallel (overlap), but are started in +sequence with successive delays. Nice for doing trailing effects.

static event(argMapping, config?) #

Takes an array of mappings and extracts values from each arg accordingly, + then calls setValue on the mapped outputs. e.g.

onScroll={Animated.event( + [{nativeEvent: {contentOffset: {x: this._scrollX}}}] + {listener}, // Optional async listener + ) + ... + onPanResponderMove: Animated.event([ + null, // raw event arg ignored + {dx: this._panX}, // gestureState arg + ]),

static createAnimatedComponent(Component) #

Make any React component Animatable. Used to create Animated.View, etc.

Properties #

Value: AnimatedValue #

Standard value class for driving animations. Typically initialized with +new Animated.Value(0);

ValueXY: AnimatedValueXY #

2D value class for driving 2D animations, such as pan gestures.

class AnimatedValue #

    Standard value for driving animations. One Animated.Value can drive +multiple properties in a synchronized fashion, but can only be driven by one +mechanism at a time. Using a new mechanism (e.g. starting a new animation, +or calling setValue) will stop any previous ones.

    Methods #

    constructor(value) #

    setValue(value) #

    Directly set the value. This will stop any animations running on the value +and update all the bound properties.

    setOffset(offset) #

    Sets an offset that is applied on top of whatever value is set, whether via +setValue, an animation, or Animated.event. Useful for compensating +things like the start of a pan gesture.

    flattenOffset(0) #

    Merges the offset value into the base value and resets the offset to zero. +The final output of the value is unchanged.

    addListener(callback) #

    Adds an asynchronous listener to the value so you can observe updates from +animations. This is useful because there is no way to +synchronously read the value because it might be driven natively.

    removeListener(id) #

    removeAllListeners(0) #

    stopAnimation(callback?) #

    Stops any running animation or tracking. callback is invoked with the +final value after stopping the animation, which is useful for updating +state to match the animation position with layout.

    interpolate(config) #

    Interpolates the value before updating the property, e.g. mapping 0-1 to +0-10.

    animate(animation, callback) #

    Typically only used internally, but could be used by a custom Animation +class.

    stopTracking(0) #

    Typically only used internally.

    track(tracking) #

    Typically only used internally.

class AnimatedValueXY #

    2D Value for driving 2D animations, such as pan gestures. Almost identical +API to normal Animated.Value, but multiplexed. Contains two regular +Animated.Values under the hood. Example:

    class DraggableView extends React.Component { + constructor(props) { + super(props); + this.state = { + pan: new Animated.ValueXY(), // inits to zero + }; + this.state.panResponder = PanResponder.create({ + onStartShouldSetPanResponder: () => true, + onPanResponderMove: Animated.event([null, { + dx: this.state.pan.x, // x,y are Animated.Value + dy: this.state.pan.y, + }]), + onPanResponderRelease: () => { + Animated.spring( + this.state.pan, // Auto-multiplexed + {toValue: {x: 0, y: 0}} // Back to zero + ).start(); + }, + }); + } + render() { + return ( + <Animated.View + {...this.state.panResponder.panHandlers} + style={this.state.pan.getLayout()}> + {this.props.children} + </Animated.View> + ); + } + }

    Methods #

    constructor(valueIn?) #

    setValue(value) #

    setOffset(offset) #

    flattenOffset(0) #

    stopAnimation(callback?) #

    addListener(callback) #

    removeListener(id) #

    getLayout(0) #

    Converts {x, y} into {left, top} for use in style, e.g.

    style={this.state.anim.getLayout()}

    getTranslateTransform(0) #

    Converts {x, y} into a useable translation transform, e.g.

    style={{ + transform: this.state.anim.getTranslateTransform() + }}

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + Animated, + Easing, + StyleSheet, + Text, + View, +} = ReactNative; +var UIExplorerButton = require('./UIExplorerButton'); + +exports.framework = 'React'; +exports.title = 'Animated - Examples'; +exports.description = 'Animated provides a powerful ' + + 'and easy-to-use API for building modern, ' + + 'interactive user experiences.'; + +exports.examples = [ + { + title: 'FadeInView', + description: 'Uses a simple timing animation to ' + + 'bring opacity from 0 to 1 when the component ' + + 'mounts.', + render: function() { + class FadeInView extends React.Component { + state: any; + + constructor(props) { + super(props); + this.state = { + fadeAnim: new Animated.Value(0), // opacity 0 + }; + } + componentDidMount() { + Animated.timing( // Uses easing functions + this.state.fadeAnim, // The value to drive + { + toValue: 1, // Target + duration: 2000, // Configuration + }, + ).start(); // Don't forget start! + } + render() { + return ( + <Animated.View // Special animatable View + style={{ + opacity: this.state.fadeAnim, // Binds + }}> + {this.props.children} + </Animated.View> + ); + } + } + class FadeInExample extends React.Component { + state: any; + + constructor(props) { + super(props); + this.state = { + show: true, + }; + } + render() { + return ( + <View> + <UIExplorerButton onPress={() => { + this.setState((state) => ( + {show: !state.show} + )); + }}> + Press to {this.state.show ? + 'Hide' : 'Show'} + </UIExplorerButton> + {this.state.show && <FadeInView> + <View style={styles.content}> + <Text>FadeInView</Text> + </View> + </FadeInView>} + </View> + ); + } + } + return <FadeInExample />; + }, + }, + { + title: 'Transform Bounce', + description: 'One `Animated.Value` is driven by a ' + + 'spring with custom constants and mapped to an ' + + 'ordered set of transforms. Each transform has ' + + 'an interpolation to convert the value into the ' + + 'right range and units.', + render: function() { + this.anim = this.anim || new Animated.Value(0); + return ( + <View> + <UIExplorerButton onPress={() => { + Animated.spring(this.anim, { + toValue: 0, // Returns to the start + velocity: 3, // Velocity makes it move + tension: -10, // Slow + friction: 1, // Oscillate a lot + }).start(); }}> + Press to Fling it! + </UIExplorerButton> + <Animated.View + style={[styles.content, { + transform: [ // Array order matters + {scale: this.anim.interpolate({ + inputRange: [0, 1], + outputRange: [1, 4], + })}, + {translateX: this.anim.interpolate({ + inputRange: [0, 1], + outputRange: [0, 500], + })}, + {rotate: this.anim.interpolate({ + inputRange: [0, 1], + outputRange: [ + '0deg', '360deg' // 'deg' or 'rad' + ], + })}, + ]} + ]}> + <Text>Transforms!</Text> + </Animated.View> + </View> + ); + }, + }, + { + title: 'Composite Animations with Easing', + description: 'Sequence, parallel, delay, and ' + + 'stagger with different easing functions.', + render: function() { + this.anims = this.anims || [1,2,3].map( + () => new Animated.Value(0) + ); + return ( + <View> + <UIExplorerButton onPress={() => { + var timing = Animated.timing; + Animated.sequence([ // One after the other + timing(this.anims[0], { + toValue: 200, + easing: Easing.linear, + }), + Animated.delay(400), // Use with sequence + timing(this.anims[0], { + toValue: 0, + easing: Easing.elastic(2), // Springy + }), + Animated.delay(400), + Animated.stagger(200, + this.anims.map((anim) => timing( + anim, {toValue: 200} + )).concat( + this.anims.map((anim) => timing( + anim, {toValue: 0} + ))), + ), + Animated.delay(400), + Animated.parallel([ + Easing.inOut(Easing.quad), // Symmetric + Easing.back(1.5), // Goes backwards first + Easing.ease // Default bezier + ].map((easing, ii) => ( + timing(this.anims[ii], { + toValue: 320, easing, duration: 3000, + }) + ))), + Animated.delay(400), + Animated.stagger(200, + this.anims.map((anim) => timing(anim, { + toValue: 0, + easing: Easing.bounce, // Like a ball + duration: 2000, + })), + ), + ]).start(); }}> + Press to Animate + </UIExplorerButton> + {['Composite', 'Easing', 'Animations!'].map( + (text, ii) => ( + <Animated.View + key={text} + style={[styles.content, { + left: this.anims[ii] + }]}> + <Text>{text}</Text> + </Animated.View> + ) + )} + </View> + ); + }, + }, + { + title: 'Continuous Interactions', + description: 'Gesture events, chaining, 2D ' + + 'values, interrupting and transitioning ' + + 'animations, etc.', + render: () => ( + <Text>Checkout the Gratuitous Animation App!</Text> + ), + } +]; + +var styles = StyleSheet.create({ + content: { + backgroundColor: 'deepskyblue', + borderWidth: 1, + borderColor: 'dodgerblue', + padding: 20, + margin: 20, + borderRadius: 10, + alignItems: 'center', + }, +});
\ No newline at end of file diff --git a/releases/0.37/docs/animations.html b/releases/0.37/docs/animations.html new file mode 100644 index 00000000000..c4c04c97947 --- /dev/null +++ b/releases/0.37/docs/animations.html @@ -0,0 +1,399 @@ +Animations

Animations #

Fluid, meaningful animations are essential to the mobile user experience. Like +everything in React Native, Animation APIs for React Native are currently under +development, but have started to coalesce around two complementary systems: +LayoutAnimation for animated global layout transactions, and Animated for +more granular and interactive control of specific values.

Animated #

The Animated library is designed to make it very easy to concisely express a +wide variety of interesting animation and interaction patterns in a very +performant way. Animated focuses on declarative relationships between inputs +and outputs, with configurable transforms in between, and simple start/stop +methods to control time-based animation execution. For example, a complete +component with a simple spring bounce on mount looks like this:

class Playground extends React.Component { + constructor(props) { + super(props); + this.state = { + bounceValue: new Animated.Value(0), + }; + } + render() { + return ( + <Animated.Image // Base: Image, Text, View + source={{uri: 'http://i.imgur.com/XMKOH81.jpg'}} + style={{ + flex: 1, + transform: [ // `transform` is an ordered array + {scale: this.state.bounceValue}, // Map `bounceValue` to `scale` + ] + }} + /> + ); + } + componentDidMount() { + this.state.bounceValue.setValue(1.5); // Start large + Animated.spring( // Base: spring, decay, timing + this.state.bounceValue, // Animate `bounceValue` + { + toValue: 0.8, // Animate to smaller size + friction: 1, // Bouncier spring + } + ).start(); // Start the animation + } +}

bounceValue is initialized as part of state in the constructor, and mapped +to the scale transform on the image. Behind the scenes, the numeric value is +extracted and used to set scale. When the component mounts, the scale is set to +1.5 and then a spring animation is started on bounceValue which will update +all of its dependent mappings on each frame as the spring animates (in this +case, just the scale). This is done in an optimized way that is faster than +calling setState and re-rendering. Because the entire configuration is +declarative, we will be able to implement further optimizations that serialize +the configuration and runs the animation on a high-priority thread.

Core API #

Most everything you need hangs directly off the Animated module. This +includes two value types, Value for single values and ValueXY for vectors, +three animation types, spring, decay, and timing, and three component +types, View, Text, and Image. You can make any other component animated with +Animated.createAnimatedComponent.

The three animation types can be used to create almost any animation curve you +want because each can be customized:

  • spring: Simple single-spring physics model that matches Origami.
    • friction: Controls "bounciness"/overshoot. Default 7.
    • tension: Controls speed. Default 40.
  • decay: Starts with an initial velocity and gradually slows to a stop.
    • velocity: Initial velocity. Required.
    • deceleration: Rate of decay. Default 0.997.
  • timing: Maps time range to easing value.
    • duration: Length of animation (milliseconds). Default 500.
    • easing: Easing function to define curve. See Easing module for several +predefined functions. iOS default is Easing.inOut(Easing.ease).
    • delay: Start the animation after delay (milliseconds). Default 0.

Animations are started by calling start. start takes a completion callback +that will be called when the animation is done. If the animation is done +because it finished running normally, the completion callback will be invoked +with {finished: true}, but if the animation is done because stop was called +on it before it could finish (e.g. because it was interrupted by a gesture or +another animation), then it will receive {finished: false}.

Composing Animations #

Animations can also be composed with parallel, sequence, stagger, and +delay, each of which simply take an array of animations to execute and +automatically calls start/stop as appropriate. For example:

Animated.sequence([ // spring to start and twirl after decay finishes + Animated.decay(position, { // coast to a stop + velocity: {x: gestureState.vx, y: gestureState.vy}, // velocity from gesture release + deceleration: 0.997, + }), + Animated.parallel([ // after decay, in parallel: + Animated.spring(position, { + toValue: {x: 0, y: 0} // return to start + }), + Animated.timing(twirl, { // and twirl + toValue: 360, + }), + ]), +]).start(); // start the sequence group

By default, if one animation is stopped or interrupted, then all other +animations in the group are also stopped. Parallel has a stopTogether option +that can be set to false to disable this.

Interpolation #

Another powerful part of the Animated API is the interpolate function. It +allows input ranges to map to different output ranges. For example, a simple +mapping to convert a 0-1 range to a 0-100 range would be

value.interpolate({ + inputRange: [0, 1], + outputRange: [0, 100], +});

interpolate supports multiple range segments as well, which is handy for +defining dead zones and other handy tricks. For example, to get an negation +relationship at -300 that goes to 0 at -100, then back up to 1 at 0, and then +back down to zero at 100 followed by a dead-zone that remains at 0 for +everything beyond that, you could do:

value.interpolate({ + inputRange: [-300, -100, 0, 100, 101], + outputRange: [300, 0, 1, 0, 0], +});

Which would map like so:

InputOutput
-400450
-300300
-200150
-1000
-500.5
01
500.5
1000
1010
2000

interpolate also supports mapping to strings, allowing you to animate colors as well as values with units. For example, if you wanted to animate a rotation you could do:

value.interpolate({ + inputRange: [0, 360], + outputRange: ['0deg', '360deg'] +})

interpolation also supports arbitrary easing functions, many of which are +already implemented in the +Easing +class including quadratic, exponential, and bezier curves as well as functions +like step and bounce. interpolation also has configurable behavior for +extrapolating the outputRange. You can set the extrapolation by setting the extrapolate, +extrapolateLeft or extrapolateRight options. The default value is +extend but you can use clamp to prevent the output value from exceeding +outputRange.

Tracking Dynamic Values #

Animated values can also track other values. Just set the toValue of an +animation to another animated value instead of a plain number, for example with +spring physics for an interaction like "Chat Heads", or via timing with +duration: 0 for rigid/instant tracking. They can also be composed with +interpolations:

Animated.spring(follower, {toValue: leader}).start(); +Animated.timing(opacity, { + toValue: pan.x.interpolate({ + inputRange: [0, 300], + outputRange: [1, 0], + }), +}).start();

ValueXY is a handy way to deal with 2D interactions, such as panning/dragging. +It is a simple wrapper that basically just contains two Animated.Value +instances and some helper functions that call through to them, making ValueXY +a drop-in replacement for Value in many cases. For example, in the code +snippet above, leader and follower could both be of type ValueXY and the x +and y values will both track as you would expect.

Input Events #

Animated.event is the input side of the Animated API, allowing gestures and +other events to map directly to animated values. This is done with a structured +map syntax so that values can be extracted from complex event objects. The +first level is an array to allow mapping across multiple args, and that array +contains nested objects. In the example, you can see that scrollX maps to +event.nativeEvent.contentOffset.x (event is normally the first arg to the +handler), and pan.x and pan.y map to gestureState.dx and gestureState.dy, +respectively (gestureState is the second arg passed to the PanResponder handler).

onScroll={Animated.event( + // scrollX = e.nativeEvent.contentOffset.x + [{nativeEvent: {contentOffset: {x: scrollX}}}] +)} +onPanResponderMove={Animated.event([ + null, // ignore the native event + // extract dx and dy from gestureState + // like 'pan.x = gestureState.dx, pan.y = gestureState.dy' + {dx: pan.x, dy: pan.y} +]);

Responding to the Current Animation Value #

You may notice that there is no obvious way to read the current value while +animating - this is because the value may only be known in the native runtime +due to optimizations. If you need to run JavaScript in response to the current +value, there are two approaches:

  • spring.stopAnimation(callback) will stop the animation and invoke callback +with the final value - this is useful when making gesture transitions.
  • spring.addListener(callback) will invoke callback asynchronously while the +animation is running, providing a recent value. This is useful for triggering +state changes, for example snapping a bobble to a new option as the user drags +it closer, because these larger state changes are less sensitive to a few frames +of lag compared to continuous gestures like panning which need to run at 60fps.

Future Work #

As previously mentioned, we're planning on optimizing Animated under the hood to +make it even more performant. We would also like to experiment with more +declarative and higher level gestures and triggers, such as horizontal vs. +vertical panning.

The above API gives a powerful tool for expressing all sorts of animations in a +concise, robust, and performant way. Check out more example code in +UIExplorer/AnimationExample. Of course there may still be times where Animated +doesn't support what you need, and the following sections cover other animation +systems.

LayoutAnimation #

LayoutAnimation allows you to globally configure create and update +animations that will be used for all views in the next render/layout cycle. +This is useful for doing flexbox layout updates without bothering to measure or +calculate specific properties in order to animate them directly, and is +especially useful when layout changes may affect ancestors, for example a "see +more" expansion that also increases the size of the parent and pushes down the +row below which would otherwise require explicit coordination between the +components in order to animate them all in sync.

Note that although LayoutAnimation is very powerful and can be quite useful, +it provides much less control than Animated and other animation libraries, so +you may need to use another approach if you can't get LayoutAnimation to do +what you want.

Note that in order to get this to work on Android you need to set the following flags via UIManager:

UIManager.setLayoutAnimationEnabledExperimental && UIManager.setLayoutAnimationEnabledExperimental(true);

class App extends React.Component { + constructor(props) { + super(props); + this.state = { w: 100, h: 100 }; + this._onPress = this._onPress.bind(this); + } + + componentWillMount() { + // Animate creation + LayoutAnimation.spring(); + } + + _onPress() { + // Animate the update + LayoutAnimation.spring(); + this.setState({w: this.state.w + 15, h: this.state.h + 15}) + } + + render() { + return ( + <View style={styles.container}> + <View style={[styles.box, {width: this.state.w, height: this.state.h}]} /> + <TouchableOpacity onPress={this._onPress}> + <View style={styles.button}> + <Text style={styles.buttonText}>Press me!</Text> + </View> + </TouchableOpacity> + </View> + ); + } +}

Run this example

This example uses a preset value, you can customize the animations as +you need, see LayoutAnimation.js +for more information.

requestAnimationFrame #

requestAnimationFrame is a polyfill from the browser that you might be +familiar with. It accepts a function as its only argument and calls that +function before the next repaint. It is an essential building block for +animations that underlies all of the JavaScript-based animation APIs. In +general, you shouldn't need to call this yourself - the animation APIs will +manage frame updates for you.

react-tween-state (Not recommended - use Animated instead) #

react-tween-state is a +minimal library that does exactly what its name suggests: it tweens a +value in a component's state, starting at a from value and ending at +a to value. This means that it generates the values in between those +two values, and it sets the state on every requestAnimationFrame with +the intermediary value.

Tweening definition from Wikipedia

"... tweening is the process of generating intermediate frames between two +images to give the appearance that the first image evolves smoothly +into the second image. [Tweens] are the drawings between the key +frames which help to create the illusion of motion."

The most obvious way to animate from one value to another is linearly: +you subtract the end value from the start value and divide the result by +the number of frames over which the animation occurs, and then add that +value to the current value on each frame until the end value is reached. +Linear easing often looks awkward and unnatural, so react-tween-state +provides a selection of popular easing functions +that can be applied to make your animations more pleasing.

This library does not ship with React Native - in order to use it on +your project, you will need to install it with npm i react-tween-state +--save from your project directory.

import tweenState from 'react-tween-state'; +import reactMixin from 'react-mixin'; // https://github.com/brigand/react-mixin + +class App extends React.Component { + constructor(props) { + super(props); + this.state = { opacity: 1 }; + this._animateOpacity = this._animateOpacity.bind(this); + } + + _animateOpacity() { + this.tweenState('opacity', { + easing: tweenState.easingTypes.easeOutQuint, + duration: 1000, + endValue: this.state.opacity === 0.2 ? 1 : 0.2, + }); + } + + render() { + return ( + <View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}> + <TouchableWithoutFeedback onPress={this._animateOpacity}> + <View ref={component => this._box = component} + style={{width: 200, height: 200, backgroundColor: 'red', + opacity: this.getTweeningValue('opacity')}} /> + </TouchableWithoutFeedback> + </View> + ) + } +} + +reactMixin.onClass(App, tweenState.Mixin);

Run this example

Here we animated the opacity, but as you might guess, we can animate any +numeric value. Read more about react-tween-state in its +README.

Rebound (Not recommended - use Animated instead) #

Rebound.js is a JavaScript port of +Rebound for Android. It is +similar in concept to react-tween-state: you have an initial value and +set an end value, then Rebound generates intermediate values that you can +use for your animation. Rebound is modeled after spring physics; we +don't provide a duration when animating with springs, it is +calculated for us depending on the spring tension, friction, current +value and end value. Rebound is used +internally +by React Native on Navigator and WarningBox.

Notice that Rebound animations can be interrupted - if you release in +the middle of a press, it will animate back from the current state to +the original value.

import rebound from 'rebound'; + +class App extends React.Component { + constructor(props) { + super(props); + this._onPressIn = this._onPressIn.bind(this); + this._onPressOut = this._onPressOut.bind(this); + } + // First we initialize the spring and add a listener, which calls + // setState whenever it updates + componentWillMount() { + // Initialize the spring that will drive animations + this.springSystem = new rebound.SpringSystem(); + this._scrollSpring = this.springSystem.createSpring(); + var springConfig = this._scrollSpring.getSpringConfig(); + springConfig.tension = 230; + springConfig.friction = 10; + + this._scrollSpring.addListener({ + onSpringUpdate: () => { + this.setState({scale: this._scrollSpring.getCurrentValue()}); + }, + }); + + // Initialize the spring value at 1 + this._scrollSpring.setCurrentValue(1); + } + + _onPressIn() { + this._scrollSpring.setEndValue(0.5); + } + + _onPressOut() { + this._scrollSpring.setEndValue(1); + } + + render() { + var imageStyle = { + width: 250, + height: 200, + transform: [{scaleX: this.state.scale}, {scaleY: this.state.scale}], + }; + + var imageUri = "img/ReboundExample.png"; + + return ( + <View style={styles.container}> + <TouchableWithoutFeedback onPressIn={this._onPressIn} + onPressOut={this._onPressOut}> + <Image source={{uri: imageUri}} style={imageStyle} /> + </TouchableWithoutFeedback> + </View> + ); + } +}

Run this example

You can also clamp the spring values so that they don't overshoot and +oscillate around the end value. In the above example, we would add +this._scrollSpring.setOvershootClampingEnabled(true) to change this. +See the below gif for an example of where in your interface you might +use this.

Screenshot from +react-native-scrollable-tab-view. +You can run a similar example here.

A sidenote about setNativeProps #

As mentioned in the Direction Manipulation section, +setNativeProps allows us to modify properties of native-backed +components (components that are actually backed by native views, unlike +composite components) directly, without having to setState and +re-render the component hierarchy.

We could use this in the Rebound example to update the scale - this +might be helpful if the component that we are updating is deeply nested +and hasn't been optimized with shouldComponentUpdate.

// Back inside of the App component, replace the scrollSpring listener +// in componentWillMount with this: +this._scrollSpring.addListener({ + onSpringUpdate: () => { + if (!this._photo) { return } + var v = this._scrollSpring.getCurrentValue(); + var newProps = {style: {transform: [{scaleX: v}, {scaleY: v}]}}; + this._photo.setNativeProps(newProps); + }, +}); + +// Lastly, we update the render function to no longer pass in the +// transform via style (avoid clashes when re-rendering) and to set the +// photo ref +render() { + return ( + <View style={styles.container}> + <TouchableWithoutFeedback onPressIn={this._onPressIn} onPressOut={this._onPressOut}> + <Image ref={component => this._photo = component} + source={{uri: "img/ReboundExample.png"}} + style={{width: 250, height: 200}} /> + </TouchableWithoutFeedback> + </View> + ); +}

Run this example

It would not make sense to use setNativeProps with react-tween-state +because the updated tween values are set on the state automatically by +the library - Rebound on the other hand gives us an updated value for +each frame with the onSpringUpdate function.

If you find your animations with dropping frames (performing below 60 +frames per second), look into using setNativeProps or +shouldComponentUpdate to optimize them. You may also want to defer any +computationally intensive work until after animations are complete, +using the +InteractionManager. You +can monitor the frame rate by using the In-App Developer Menu "FPS +Monitor" tool.

Navigator Scene Transitions #

As mentioned in the Navigator +Comparison, +Navigator is implemented in JavaScript and NavigatorIOS is a wrapper +around native functionality provided by UINavigationController, so +these scene transitions apply only to Navigator. In order to re-create +the various animations provided by UINavigationController and also +make them customizable, React Native exposes a +NavigatorSceneConfigs API which is then handed over to the Navigator configureScene prop.

import { Dimensions } from 'react-native'; +var SCREEN_WIDTH = Dimensions.get('window').width; +var BaseConfig = Navigator.SceneConfigs.FloatFromRight; + +var CustomLeftToRightGesture = Object.assign({}, BaseConfig.gestures.pop, { + // Make it snap back really quickly after canceling pop + snapVelocity: 8, + + // Make it so we can drag anywhere on the screen + edgeHitWidth: SCREEN_WIDTH, +}); + +var CustomSceneConfig = Object.assign({}, BaseConfig, { + // A very tightly wound spring will make this transition fast + springTension: 100, + springFriction: 1, + + // Use our custom gesture defined above + gestures: { + pop: CustomLeftToRightGesture, + } +});

Run this example

For further information about customizing scene transitions, read the +source.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/appregistry.html b/releases/0.37/docs/appregistry.html new file mode 100644 index 00000000000..76844097f00 --- /dev/null +++ b/releases/0.37/docs/appregistry.html @@ -0,0 +1,33 @@ +AppRegistry

AppRegistry #

AppRegistry is the JS entry point to running all React Native apps. App +root components should register themselves with +AppRegistry.registerComponent, then the native system can load the bundle +for the app and then actually run the app when it's ready by invoking +AppRegistry.runApplication.

To "stop" an application when a view should be destroyed, call +AppRegistry.unmountApplicationComponentAtRootTag with the tag that was +pass into runApplication. These should always be used as a pair.

AppRegistry should be required early in the require sequence to make +sure the JS execution environment is setup before other modules are +required.

Methods #

static registerConfig(config) #

static registerComponent(appKey, getComponentFunc) #

static registerRunnable(appKey, func) #

static getAppKeys(0) #

static runApplication(appKey, appParameters) #

static unmountApplicationComponentAtRootTag(rootTag) #

static registerHeadlessTask(taskKey, task) #

Register a headless task. A headless task is a bit of code that runs without a UI. +@param taskKey the key associated with this task +@param task a promise returning function that takes some data passed from the native side as + the only argument; when the promise is resolved or rejected the native side is + notified of this event and it may decide to destroy the JS context.

static startHeadlessTask(taskId, taskKey, data) #

Only called from native code. Starts a headless task.

@param taskId the native id for this task instance to keep track of its execution +@param taskKey the key for the task to start +@param data the data to pass to the task

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/appstate.html b/releases/0.37/docs/appstate.html new file mode 100644 index 00000000000..fb22ffd86a5 --- /dev/null +++ b/releases/0.37/docs/appstate.html @@ -0,0 +1,138 @@ +AppState

AppState #

AppState can tell you if the app is in the foreground or background, +and notify you when the state changes.

AppState is frequently used to determine the intent and proper behavior when +handling push notifications.

App States #

  • active - The app is running in the foreground
  • background - The app is running in the background. The user is either + in another app or on the home screen
  • inactive - This is a state that occurs when transitioning between + foreground & background, and during periods of inactivity such as + entering the Multitasking view or in the event of an incoming call

For more information, see +Apple's documentation

Basic Usage #

To see the current state, you can check AppState.currentState, which +will be kept up-to-date. However, currentState will be null at launch +while AppState retrieves it over the bridge.

getInitialState: function() { + return { + currentAppState: AppState.currentState, + }; +}, +componentDidMount: function() { + AppState.addEventListener('change', this._handleAppStateChange); +}, +componentWillUnmount: function() { + AppState.removeEventListener('change', this._handleAppStateChange); +}, +_handleAppStateChange: function(currentAppState) { + this.setState({ currentAppState, }); +}, +render: function() { + return ( + <Text>Current state is: {this.state.currentAppState}</Text> + ); +},

This example will only ever appear to say "Current state is: active" because +the app is only visible to the user when in the active state, and the null +state will happen only momentarily.

Methods #

constructor(0) #

addEventListener(type, handler) #

Add a handler to AppState changes by listening to the change event type +and providing the handler

TODO: now that AppState is a subclass of NativeEventEmitter, we could deprecate +addEventListener and removeEventListener and just use addListener and +listener.remove() directly. That will be a breaking change though, as both +the method and event names are different (addListener events are currently +required to be globally unique).

removeEventListener(type, handler) #

Remove a handler by passing the change event type and the handler

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +const React = require('react'); +const ReactNative = require('react-native'); +const { + AppState, + Text, + View +} = ReactNative; + +class AppStateSubscription extends React.Component { + state = { + appState: AppState.currentState, + previousAppStates: [], + memoryWarnings: 0, + }; + + componentDidMount() { + AppState.addEventListener('change', this._handleAppStateChange); + AppState.addEventListener('memoryWarning', this._handleMemoryWarning); + } + + componentWillUnmount() { + AppState.removeEventListener('change', this._handleAppStateChange); + AppState.removeEventListener('memoryWarning', this._handleMemoryWarning); + } + + _handleMemoryWarning = () => { + this.setState({memoryWarnings: this.state.memoryWarnings + 1}); + }; + + _handleAppStateChange = (appState) => { + var previousAppStates = this.state.previousAppStates.slice(); + previousAppStates.push(this.state.appState); + this.setState({ + appState, + previousAppStates, + }); + }; + + render() { + if (this.props.showMemoryWarnings) { + return ( + <View> + <Text>{this.state.memoryWarnings}</Text> + </View> + ); + } + if (this.props.showCurrentOnly) { + return ( + <View> + <Text>{this.state.appState}</Text> + </View> + ); + } + return ( + <View> + <Text>{JSON.stringify(this.state.previousAppStates)}</Text> + </View> + ); + } +} + +exports.title = 'AppState'; +exports.description = 'app background status'; +exports.examples = [ + { + title: 'AppState.currentState', + description: 'Can be null on app initialization', + render() { return <Text>{AppState.currentState}</Text>; } + }, + { + title: 'Subscribed AppState:', + description: 'This changes according to the current state, so you can only ever see it rendered as "active"', + render(): React.Element<any> { return <AppStateSubscription showCurrentOnly={true} />; } + }, + { + title: 'Previous states:', + render(): React.Element<any> { return <AppStateSubscription showCurrentOnly={false} />; } + }, + { + platform: 'ios', + title: 'Memory Warnings', + description: 'In the IOS simulator, hit Shift+Command+M to simulate a memory warning.', + render(): React.Element<any> { return <AppStateSubscription showMemoryWarnings={true} />; } + }, +];
\ No newline at end of file diff --git a/releases/0.37/docs/asyncstorage.html b/releases/0.37/docs/asyncstorage.html new file mode 100644 index 00000000000..25c1dabedf2 --- /dev/null +++ b/releases/0.37/docs/asyncstorage.html @@ -0,0 +1,238 @@ +AsyncStorage

AsyncStorage #

AsyncStorage is a simple, unencrypted, asynchronous, persistent, key-value storage +system that is global to the app. It should be used instead of LocalStorage.

It is recommended that you use an abstraction on top of AsyncStorage +instead of AsyncStorage directly for anything more than light usage since +it operates globally.

On iOS, AsyncStorage is backed by native code that stores small values in a +serialized dictionary and larger values in separate files. On Android, +AsyncStorage will use either RocksDB or SQLite +based on what is available.

The AsyncStorage JavaScript code is a simple facade that provides a clear +JavaScript API, real Error objects, and simple non-multi functions. Each +method in the API returns a Promise object.

Persisting data:

try { + await AsyncStorage.setItem('@MySuperStore:key', 'I like to save it.'); +} catch (error) { + // Error saving data +}

Fetching data:

try { + const value = await AsyncStorage.getItem('@MySuperStore:key'); + if (value !== null){ + // We have data!! + console.log(value); + } +} catch (error) { + // Error retrieving data +}

Methods #

static getItem(key, callback?) #

Fetches an item for a key and invokes a callback upon completion. +Returns a Promise object.

Parameters:
Name and TypeDescription
key

string

Key of the item to fetch.

[callback]

?(error: ?Error, result: ?string) => void

Function that will be called with a result if found or + any error.

static setItem(key, value, callback?) #

Sets the value for a key and invokes a callback upon completion. +Returns a Promise object.

Parameters:
Name and TypeDescription
key

string

Key of the item to set.

value

string

Value to set for the key.

[callback]

?(error: ?Error) => void

Function that will be called with any error.

static removeItem(key, callback?) #

Removes an item for a key and invokes a callback upon completion. +Returns a Promise object.

Parameters:
Name and TypeDescription
key

string

Key of the item to remove.

[callback]

?(error: ?Error) => void

Function that will be called with any error.

static mergeItem(key, value, callback?) #

Merges an existing key value with an input value, assuming both values +are stringified JSON. Returns a Promise object.

NOTE: This is not supported by all native implementations.

Parameters:
Name and TypeDescription
key

string

Key of the item to modify.

value

string

New value to merge for the key.

[callback]

?(error: ?Error) => void

Function that will be called with any error.


Example:
+let UID123_object = { + name: 'Chris', + age: 30, + traits: {hair: 'brown', eyes: 'brown'}, +}; +// You only need to define what will be added or updated +let UID123_delta = { + age: 31, + traits: {eyes: 'blue', shoe_size: 10} +}; + +AsyncStorage.setItem('UID123', JSON.stringify(UID123_object), () => { + AsyncStorage.mergeItem('UID123', JSON.stringify(UID123_delta), () => { + AsyncStorage.getItem('UID123', (err, result) => { + console.log(result); + }); + }); +}); + +// Console log result: +// => {'name':'Chris','age':31,'traits': +// {'shoe_size':10,'hair':'brown','eyes':'blue'}}

static clear(callback?) #

Erases all AsyncStorage for all clients, libraries, etc. You probably +don't want to call this; use removeItem or multiRemove to clear only +your app's keys. Returns a Promise object.

Parameters:
Name and TypeDescription
[callback]

?(error: ?Error) => void

Function that will be called with any error.

static getAllKeys(callback?) #

Gets all keys known to your app; for all callers, libraries, etc. +Returns a Promise object.

Parameters:
Name and TypeDescription
[callback]

?(error: ?Error, keys: ?Array<string>) => void

Function that will be called the keys found and any error.

static flushGetRequests() #

Flushes any pending requests using a single batch call to get the data.

static multiGet(keys, callback?) #

This allows you to batch the fetching of items given an array of key +inputs. Your callback will be invoked with an array of corresponding +key-value pairs found:

multiGet(['k1', 'k2'], cb) -> cb([['k1', 'val1'], ['k2', 'val2']])

The method returns a Promise object.

Parameters:
Name and TypeDescription
keys

Array<string>

Array of key for the items to get.

[callback]

?(errors: ?Array<Error>, result: ?Array<Array<string>>) => void

Function that will be called with a key-value array of + the results, plus an array of any key-specific errors found.


Example:
AsyncStorage.getAllKeys((err, keys) => { + AsyncStorage.multiGet(keys, (err, stores) => { + stores.map((result, i, store) => { + // get at each store's key/value so you can work with it + let key = store[i][0]; + let value = store[i][1]; + }); + }); +});

static multiSet(keyValuePairs, callback?) #

Use this as a batch operation for storing multiple key-value pairs. When +the operation completes you'll get a single callback with any errors:

multiSet([['k1', 'val1'], ['k2', 'val2']], cb);

The method returns a Promise object.

Parameters:
Name and TypeDescription
keyValuePairs

Array<Array<string>>

Array of key-value array for the items to set.

[callback]

?(errors: ?Array<Error>) => void

Function that will be called with an array of any + key-specific errors found.

static multiRemove(keys, callback?) #

Call this to batch the deletion of all keys in the keys array. Returns +a Promise object.

Parameters:
Name and TypeDescription
keys

Array<string>

Array of key for the items to delete.

[callback]

?(errors: ?Array<Error>) => void

Function that will be called an array of any key-specific + errors found.


Example:
+let keys = ['k1', 'k2']; +AsyncStorage.multiRemove(keys, (err) => { + // keys k1 & k2 removed, if they existed + // do most stuff after removal (if you want) +});

static multiMerge(keyValuePairs, callback?) #

Batch operation to merge in existing and new values for a given set of +keys. This assumes that the values are stringified JSON. Returns a +Promise object.

NOTE: This is not supported by all native implementations.

Parameters:
Name and TypeDescription
keyValuePairs

Array<Array<string>>

Array of key-value array for the items to merge.

[callback]

?(errors: ?Array<Error>) => void

Function that will be called with an array of any + key-specific errors found.


Example:
+// first user, initial values +let UID234_object = { + name: 'Chris', + age: 30, + traits: {hair: 'brown', eyes: 'brown'}, +}; + +// first user, delta values +let UID234_delta = { + age: 31, + traits: {eyes: 'blue', shoe_size: 10}, +}; + +// second user, initial values +let UID345_object = { + name: 'Marge', + age: 25, + traits: {hair: 'blonde', eyes: 'blue'}, +}; + +// second user, delta values +let UID345_delta = { + age: 26, + traits: {eyes: 'green', shoe_size: 6}, +}; + +let multi_set_pairs = [['UID234', JSON.stringify(UID234_object)], ['UID345', JSON.stringify(UID345_object)]] +let multi_merge_pairs = [['UID234', JSON.stringify(UID234_delta)], ['UID345', JSON.stringify(UID345_delta)]] + +AsyncStorage.multiSet(multi_set_pairs, (err) => { + AsyncStorage.multiMerge(multi_merge_pairs, (err) => { + AsyncStorage.multiGet(['UID234','UID345'], (err, stores) => { + stores.map( (result, i, store) => { + let key = store[i][0]; + let val = store[i][1]; + console.log(key, val); + }); + }); + }); +}); + +// Console log results: +// => UID234 {"name":"Chris","age":31,"traits":{"shoe_size":10,"hair":"brown","eyes":"blue"}} +// => UID345 {"name":"Marge","age":26,"traits":{"shoe_size":6,"hair":"blonde","eyes":"green"}}

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + AsyncStorage, + PickerIOS, + Text, + View +} = ReactNative; +var PickerItemIOS = PickerIOS.Item; + +var STORAGE_KEY = '@AsyncStorageExample:key'; +var COLORS = ['red', 'orange', 'yellow', 'green', 'blue']; + +class BasicStorageExample extends React.Component { + state = { + selectedValue: COLORS[0], + messages: [], + }; + + componentDidMount() { + this._loadInitialState().done(); + } + + _loadInitialState = async () => { + try { + var value = await AsyncStorage.getItem(STORAGE_KEY); + if (value !== null){ + this.setState({selectedValue: value}); + this._appendMessage('Recovered selection from disk: ' + value); + } else { + this._appendMessage('Initialized with no selection on disk.'); + } + } catch (error) { + this._appendMessage('AsyncStorage error: ' + error.message); + } + }; + + render() { + var color = this.state.selectedValue; + return ( + <View> + <PickerIOS + selectedValue={color} + onValueChange={this._onValueChange}> + {COLORS.map((value) => ( + <PickerItemIOS + key={value} + value={value} + label={value} + /> + ))} + </PickerIOS> + <Text> + {'Selected: '} + <Text style={{color}}> + {this.state.selectedValue} + </Text> + </Text> + <Text>{' '}</Text> + <Text onPress={this._removeStorage}> + Press here to remove from storage. + </Text> + <Text>{' '}</Text> + <Text>Messages:</Text> + {this.state.messages.map((m) => <Text key={m}>{m}</Text>)} + </View> + ); + } + + _onValueChange = async (selectedValue) => { + this.setState({selectedValue}); + try { + await AsyncStorage.setItem(STORAGE_KEY, selectedValue); + this._appendMessage('Saved selection to disk: ' + selectedValue); + } catch (error) { + this._appendMessage('AsyncStorage error: ' + error.message); + } + }; + + _removeStorage = async () => { + try { + await AsyncStorage.removeItem(STORAGE_KEY); + this._appendMessage('Selection removed from disk.'); + } catch (error) { + this._appendMessage('AsyncStorage error: ' + error.message); + } + }; + + _appendMessage = (message) => { + this.setState({messages: this.state.messages.concat(message)}); + }; +} + +exports.title = 'AsyncStorage'; +exports.description = 'Asynchronous local disk storage.'; +exports.examples = [ + { + title: 'Basics - getItem, setItem, removeItem', + render(): React.Element<any> { return <BasicStorageExample />; } + }, +];
\ No newline at end of file diff --git a/releases/0.37/docs/backandroid.html b/releases/0.37/docs/backandroid.html new file mode 100644 index 00000000000..6edb169a27e --- /dev/null +++ b/releases/0.37/docs/backandroid.html @@ -0,0 +1,29 @@ +BackAndroid

BackAndroid #

Detect hardware back button presses, and programmatically invoke the default back button +functionality to exit the app if there are no listeners or if none of the listeners return true.

Example:

BackAndroid.addEventListener('hardwareBackPress', function() { + // this.onMainScreen and this.goBack are just examples, you need to use your own implementation here + // Typically you would use the navigator here to go to the last state. + + if (!this.onMainScreen()) { + this.goBack(); + return true; + } + return false; +});

Methods #

static exitApp(0) #

static addEventListener(eventName, handler) #

static removeEventListener(eventName, handler) #

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/button.html b/releases/0.37/docs/button.html new file mode 100644 index 00000000000..5d75afa2456 --- /dev/null +++ b/releases/0.37/docs/button.html @@ -0,0 +1,118 @@ +Button

Button #

A basic button component that should render nicely on any platform. Supports +a minimal level of customization.

+ +

If this button doesn't look right for your app, you can build your own +button using TouchableOpacity +or TouchableNativeFeedback. +For inspiration, look at the source code for this button component. +Or, take a look at the wide variety of button components built by the community.

Example usage:

<Button + onPress={onPressLearnMore} + title="Learn More" + color="#841584" + accessibilityLabel="Learn more about this purple button" +/>

Props #

accessibilityLabel string #

Text to display for blindness accessibility features

color color #

Color of the text (iOS), or background color of the button (Android)

disabled bool #

If true, disable all interactions for this component.

onPress function #

Handler to be called when the user taps the button

title string #

Text to display inside the button

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +const React = require('react'); +const ReactNative = require('react-native'); +const { + Alert, + Button, + View, +} = ReactNative; + +const onButtonPress = () => { + Alert.alert('Button has been pressed!'); +}; + +exports.displayName = 'ButtonExample'; +exports.framework = 'React'; +exports.title = '<Button>'; +exports.description = 'Simple React Native button component.'; + +exports.examples = [ + { + title: 'Simple Button', + description: 'The title and onPress handler are required. It is ' + + 'recommended to set accessibilityLabel to help make your app usable by ' + + 'everyone.', + render: function() { + return ( + <Button + onPress={onButtonPress} + title="Press Me" + accessibilityLabel="See an informative alert" + /> + ); + }, + }, + { + title: 'Adjusted color', + description: 'Adjusts the color in a way that looks standard on each ' + + 'platform. On iOS, the color prop controls the color of the text. On ' + + 'Android, the color adjusts the background color of the button.', + render: function() { + return ( + <Button + onPress={onButtonPress} + title="Press Purple" + color="#841584" + accessibilityLabel="Learn more about purple" + /> + ); + }, + }, + { + title: 'Fit to text layout', + description: 'This layout strategy lets the title define the width of ' + + 'the button', + render: function() { + return ( + <View style={{flexDirection: 'row', justifyContent: 'space-between'}}> + <Button + onPress={onButtonPress} + title="This looks great!" + accessibilityLabel="This sounds great!" + /> + <Button + onPress={onButtonPress} + title="Ok!" + color="#841584" + accessibilityLabel="Ok, Great!" + /> + </View> + ); + }, + }, + { + title: 'Disabled Button', + description: 'All interactions for the component are disabled.', + render: function() { + return ( + <Button + disabled + onPress={onButtonPress} + title="I Am Disabled" + accessibilityLabel="See an informative alert" + /> + ); + }, + }, +];
\ No newline at end of file diff --git a/releases/0.37/docs/cameraroll.html b/releases/0.37/docs/cameraroll.html new file mode 100644 index 00000000000..eb6d12f4b7a --- /dev/null +++ b/releases/0.37/docs/cameraroll.html @@ -0,0 +1,150 @@ +CameraRoll

CameraRoll #

CameraRoll provides access to the local camera roll / gallery. +Before using this you must link the RCTCameraRoll library. +You can refer to Linking for help.

Methods #

static saveImageWithTag(tag) #

static saveToCameraRoll(tag, type?) #

Saves the photo or video to the camera roll / gallery.

On Android, the tag must be a local image or video URI, such as "file:///sdcard/img.png".

On iOS, the tag can be any image URI (including local, remote asset-library and base64 data URIs) +or a local video file URI (remote or data URIs are not supported for saving video at this time).

If the tag has a file extension of .mov or .mp4, it will be inferred as a video. Otherwise +it will be treated as a photo. To override the automatic choice, you can pass an optional +type parameter that must be one of 'photo' or 'video'.

Returns a Promise which will resolve with the new URI.

static getPhotos(params) #

Returns a Promise with photo identifier objects from the local camera +roll of the device matching shape defined by getPhotosReturnChecker.

@param {object} params See getPhotosParamChecker.

Returns a Promise which when resolved will be of shape getPhotosReturnChecker.

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +const React = require('react'); +const ReactNative = require('react-native'); +const { + CameraRoll, + Image, + Slider, + StyleSheet, + Switch, + Text, + View, + TouchableOpacity +} = ReactNative; + +const invariant = require('fbjs/lib/invariant'); + +const CameraRollView = require('./CameraRollView'); + +const AssetScaledImageExampleView = require('./AssetScaledImageExample'); + +class CameraRollExample extends React.Component { + state = { + groupTypes: 'SavedPhotos', + sliderValue: 1, + bigImages: true, + }; + _cameraRollView: ?CameraRollView; + render() { + return ( + <View> + <Switch + onValueChange={this._onSwitchChange} + value={this.state.bigImages} + /> + <Text>{(this.state.bigImages ? 'Big' : 'Small') + ' Images'}</Text> + <Slider + value={this.state.sliderValue} + onValueChange={this._onSliderChange} + /> + <Text>{'Group Type: ' + this.state.groupTypes}</Text> + <CameraRollView + ref={(ref) => { this._cameraRollView = ref; }} + batchSize={20} + groupTypes={this.state.groupTypes} + renderImage={this._renderImage} + /> + </View> + ); + } + + loadAsset = (asset) => { + if (this.props.navigator) { + this.props.navigator.push({ + title: 'Camera Roll Image', + component: AssetScaledImageExampleView, + backButtonTitle: 'Back', + passProps: { asset: asset }, + }); + } + }; + + _renderImage = (asset) => { + const imageSize = this.state.bigImages ? 150 : 75; + const imageStyle = [styles.image, {width: imageSize, height: imageSize}]; + const {location} = asset.node; + const locationStr = location ? JSON.stringify(location) : 'Unknown location'; + return ( + <TouchableOpacity key={asset} onPress={ this.loadAsset.bind( this, asset ) }> + <View style={styles.row}> + <Image + source={asset.node.image} + style={imageStyle} + /> + <View style={styles.info}> + <Text style={styles.url}>{asset.node.image.uri}</Text> + <Text>{locationStr}</Text> + <Text>{asset.node.group_name}</Text> + <Text>{new Date(asset.node.timestamp).toString()}</Text> + </View> + </View> + </TouchableOpacity> + ); + }; + + _onSliderChange = (value) => { + const options = CameraRoll.GroupTypesOptions; + const index = Math.floor(value * options.length * 0.99); + const groupTypes = options[index]; + if (groupTypes !== this.state.groupTypes) { + this.setState({groupTypes: groupTypes}); + } + }; + + _onSwitchChange = (value) => { + invariant(this._cameraRollView, 'ref should be set'); + this._cameraRollView.rendererChanged(); + this.setState({ bigImages: value }); + }; +} + +const styles = StyleSheet.create({ + row: { + flexDirection: 'row', + flex: 1, + }, + url: { + fontSize: 9, + marginBottom: 14, + }, + image: { + margin: 4, + }, + info: { + flex: 1, + }, +}); + +exports.title = 'Camera Roll'; +exports.description = 'Example component that uses CameraRoll to list user\'s photos'; +exports.examples = [ + { + title: 'Photos', + render(): React.Element<any> { return <CameraRollExample />; } + } +];
\ No newline at end of file diff --git a/releases/0.37/docs/clipboard.html b/releases/0.37/docs/clipboard.html new file mode 100644 index 00000000000..0f9c35916e6 --- /dev/null +++ b/releases/0.37/docs/clipboard.html @@ -0,0 +1,71 @@ +Clipboard

Clipboard #

Clipboard gives you an interface for setting and getting content from Clipboard on both iOS and Android

Methods #

static getString(0) #

Get content of string type, this method returns a Promise, so you can use following code to get clipboard content

async _getContent() { + var content = await Clipboard.getString(); +}

static setString(content) #

Set content of string type. You can use following code to set clipboard content

_setContent() { + Clipboard.setString('hello world'); +}

@param the content to be stored in the clipboard.

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + Clipboard, + View, + Text, +} = ReactNative; + +class ClipboardExample extends React.Component { + state = { + content: 'Content will appear here' + }; + + _setClipboardContent = async () => { + Clipboard.setString('Hello World'); + try { + var content = await Clipboard.getString(); + this.setState({content}); + } catch (e) { + this.setState({content:e.message}); + } + }; + + render() { + return ( + <View> + <Text onPress={this._setClipboardContent} style={{color: 'blue'}}> + Tap to put "Hello World" in the clipboard + </Text> + <Text style={{color: 'red', marginTop: 20}}> + {this.state.content} + </Text> + </View> + ); + } +} + +exports.title = 'Clipboard'; +exports.description = 'Show Clipboard contents.'; +exports.examples = [ + { + title: 'Clipboard.setString() and getString()', + render() { + return <ClipboardExample/>; + } + } +];
\ No newline at end of file diff --git a/releases/0.37/docs/colors.html b/releases/0.37/docs/colors.html new file mode 100644 index 00000000000..ff74321e4c1 --- /dev/null +++ b/releases/0.37/docs/colors.html @@ -0,0 +1,19 @@ +Colors

Colors #

The following formats are supported:

  • '#f0f' (#rgb)
  • '#f0fc' (#rgba)
  • '#ff00ff' (#rrggbb)
  • '#ff00ff00' (#rrggbbaa)
  • 'rgb(255, 255, 255)'
  • 'rgba(255, 255, 255, 1.0)'
  • 'hsl(360, 100%, 100%)'
  • 'hsla(360, 100%, 100%, 1.0)'
  • 'transparent'
  • 'red'
  • 0xff00ff00 (0xrrggbbaa)

For the named colors, React Native follows the CSS3 specification:

  • aliceblue (#f0f8ff)
  • antiquewhite (#faebd7)
  • aqua (#00ffff)
  • aquamarine (#7fffd4)
  • azure (#f0ffff)
  • beige (#f5f5dc)
  • bisque (#ffe4c4)
  • black (#000000)
  • blanchedalmond (#ffebcd)
  • blue (#0000ff)
  • blueviolet (#8a2be2)
  • brown (#a52a2a)
  • burlywood (#deb887)
  • cadetblue (#5f9ea0)
  • chartreuse (#7fff00)
  • chocolate (#d2691e)
  • coral (#ff7f50)
  • cornflowerblue (#6495ed)
  • cornsilk (#fff8dc)
  • crimson (#dc143c)
  • cyan (#00ffff)
  • darkblue (#00008b)
  • darkcyan (#008b8b)
  • darkgoldenrod (#b8860b)
  • darkgray (#a9a9a9)
  • darkgreen (#006400)
  • darkgrey (#a9a9a9)
  • darkkhaki (#bdb76b)
  • darkmagenta (#8b008b)
  • darkolivegreen (#556b2f)
  • darkorange (#ff8c00)
  • darkorchid (#9932cc)
  • darkred (#8b0000)
  • darksalmon (#e9967a)
  • darkseagreen (#8fbc8f)
  • darkslateblue (#483d8b)
  • darkslategray (#2f4f4f)
  • darkslategrey (#2f4f4f)
  • darkturquoise (#00ced1)
  • darkviolet (#9400d3)
  • deeppink (#ff1493)
  • deepskyblue (#00bfff)
  • dimgray (#696969)
  • dimgrey (#696969)
  • dodgerblue (#1e90ff)
  • firebrick (#b22222)
  • floralwhite (#fffaf0)
  • forestgreen (#228b22)
  • fuchsia (#ff00ff)
  • gainsboro (#dcdcdc)
  • ghostwhite (#f8f8ff)
  • gold (#ffd700)
  • goldenrod (#daa520)
  • gray (#808080)
  • green (#008000)
  • greenyellow (#adff2f)
  • grey (#808080)
  • honeydew (#f0fff0)
  • hotpink (#ff69b4)
  • indianred (#cd5c5c)
  • indigo (#4b0082)
  • ivory (#fffff0)
  • khaki (#f0e68c)
  • lavender (#e6e6fa)
  • lavenderblush (#fff0f5)
  • lawngreen (#7cfc00)
  • lemonchiffon (#fffacd)
  • lightblue (#add8e6)
  • lightcoral (#f08080)
  • lightcyan (#e0ffff)
  • lightgoldenrodyellow (#fafad2)
  • lightgray (#d3d3d3)
  • lightgreen (#90ee90)
  • lightgrey (#d3d3d3)
  • lightpink (#ffb6c1)
  • lightsalmon (#ffa07a)
  • lightseagreen (#20b2aa)
  • lightskyblue (#87cefa)
  • lightslategray (#778899)
  • lightslategrey (#778899)
  • lightsteelblue (#b0c4de)
  • lightyellow (#ffffe0)
  • lime (#00ff00)
  • limegreen (#32cd32)
  • linen (#faf0e6)
  • magenta (#ff00ff)
  • maroon (#800000)
  • mediumaquamarine (#66cdaa)
  • mediumblue (#0000cd)
  • mediumorchid (#ba55d3)
  • mediumpurple (#9370db)
  • mediumseagreen (#3cb371)
  • mediumslateblue (#7b68ee)
  • mediumspringgreen (#00fa9a)
  • mediumturquoise (#48d1cc)
  • mediumvioletred (#c71585)
  • midnightblue (#191970)
  • mintcream (#f5fffa)
  • mistyrose (#ffe4e1)
  • moccasin (#ffe4b5)
  • navajowhite (#ffdead)
  • navy (#000080)
  • oldlace (#fdf5e6)
  • olive (#808000)
  • olivedrab (#6b8e23)
  • orange (#ffa500)
  • orangered (#ff4500)
  • orchid (#da70d6)
  • palegoldenrod (#eee8aa)
  • palegreen (#98fb98)
  • paleturquoise (#afeeee)
  • palevioletred (#db7093)
  • papayawhip (#ffefd5)
  • peachpuff (#ffdab9)
  • peru (#cd853f)
  • pink (#ffc0cb)
  • plum (#dda0dd)
  • powderblue (#b0e0e6)
  • purple (#800080)
  • rebeccapurple (#663399)
  • red (#ff0000)
  • rosybrown (#bc8f8f)
  • royalblue (#4169e1)
  • saddlebrown (#8b4513)
  • salmon (#fa8072)
  • sandybrown (#f4a460)
  • seagreen (#2e8b57)
  • seashell (#fff5ee)
  • sienna (#a0522d)
  • silver (#c0c0c0)
  • skyblue (#87ceeb)
  • slateblue (#6a5acd)
  • slategray (#708090)
  • slategrey (#708090)
  • snow (#fffafa)
  • springgreen (#00ff7f)
  • steelblue (#4682b4)
  • tan (#d2b48c)
  • teal (#008080)
  • thistle (#d8bfd8)
  • tomato (#ff6347)
  • turquoise (#40e0d0)
  • violet (#ee82ee)
  • wheat (#f5deb3)
  • white (#ffffff)
  • whitesmoke (#f5f5f5)
  • yellow (#ffff00)
  • yellowgreen (#9acd32)

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/communication-ios.html b/releases/0.37/docs/communication-ios.html new file mode 100644 index 00000000000..24ab382f796 --- /dev/null +++ b/releases/0.37/docs/communication-ios.html @@ -0,0 +1,95 @@ +Communication between native and React Native

Communication between native and React Native #

In Integrating with Existing Apps guide and Native UI Components guide we learn how to embed React Native in a native component and vice versa. When we mix native and React Native components, we'll eventually find a need to communicate between these two worlds. Some ways to achieve that have been already mentioned in other guides. This article summarizes available techniques.

Introduction #

React Native is inspired by React, so the basic idea of the information flow is similar. The flow in React is one-directional. We maintain a hierarchy of components, in which each component depends only on its parent and own internal state. We do this with properties: data is passed from a parent to its children in a top-down manner. If we have an ancestor component that rely on the state of its descendant, the recommended solution would be to pass down a callback that would be used by the descendant to update the ancestor.

The same concept applies to React Native. As long as we are building our application purely within the framework, we can drive our app with properties and callbacks. But, when we mix React Native and native components, we need some special, cross-language mechanisms that would allow us to pass information between them.

Properties #

Properties are the simplest way of cross-component communication. So we need a way to pass properties both from native to React Native, and from React Native to native.

Passing properties from native to React Native #

In order to embed a React Native view in a native component, we use RCTRootView. RCTRootView is a UIView that holds a React Native app. It also provides an interface between native side and the hosted app.

RCTRootView has an initializer that allows you to pass arbitrary properties down to the React Native app. The initialProperties parameter has to be an instance of NSDictionary. The dictionary is internally converted into a JSON object that the top-level JS component can reference.

NSArray *imageList = @[@"http://foo.com/bar1.png", + @"http://foo.com/bar2.png"]; + +NSDictionary *props = @{@"images" : imageList}; + +RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge + moduleName:@"ImageBrowserApp" + initialProperties:props];
'use strict'; + +import React from 'react'; +import { + AppRegistry, + View, + Image +} from 'react-native'; + +class ImageBrowserApp extends React.Component { + renderImage(imgURI) { + return ( + <Image source={{uri: imgURI}} /> + ); + } + render() { + return ( + <View> + {this.props.images.map(this.renderImage)} + </View> + ); + } +} + +AppRegistry.registerComponent('ImageBrowserApp', () => ImageBrowserApp);

RCTRootView also provides a read-write property appProperties. After appProperties is set, the React Native app is re-rendered with new properties. The update is only performed when the new updated properties differ from the previous ones.

NSArray *imageList = @[@"http://foo.com/bar3.png", + @"http://foo.com/bar4.png"]; + +rootView.appProperties = @{@"images" : imageList};

It is fine to update properties anytime. However, updates have to be performed on the main thread. You use the getter on any thread.

There is no way to update only a few properties at a time. We suggest that you build it into your own wrapper instead.

Note: +Currently, JS functions componentWillReceiveProps and componentWillUpdateProps of the top level RN component will not be called after a prop update. However, you can access the new props in componentWillMount function.

Passing properties from React Native to native #

The problem exposing properties of native components is covered in detail in this article. In short, export properties with RCT_CUSTOM_VIEW_PROPERTY macro in your custom native component, then just use them in React Native as if the component was an ordinary React Native component.

Limits of properties #

The main drawback of cross-language properties is that they do not support callbacks, which would allow us to handle bottom-up data bindings. Imagine you have a small RN view that you want to be removed from the native parent view as a result of a JS action. There is no way to do that with props, as the information would need to go bottom-up.

Although we have a flavor of cross-language callbacks (described here), these callbacks are not always the thing we need. The main problem is that they are not intended to be passed as properties. Rather, this mechanism allows us to trigger a native action from JS, and handle the result of that action in JS.

Other ways of cross-language interaction (events and native modules) #

As stated in the previous chapter, using properties comes with some limitations. Sometimes properties are not enough to drive the logic of our app and we need a solution that gives more flexibility. This chapter covers other communication techniques available in React Native. They can be used for internal communication (between JS and native layers in RN) as well as for external communication (between RN and the 'pure native' part of your app).

React Native enables you to perform cross-language function calls. You can execute custom native code from JS and vice versa. Unfortunately, depending on the side we are working on, we achieve the same goal in different ways. For native - we use events mechanism to schedule an execution of a handler function in JS, while for React Native we directly call methods exported by native modules.

Calling React Native functions from native (events) #

Events are described in detail in this article. Note that using events gives us no guarantees about execution time, as the event is handled on a separate thread.

Events are powerful, because they allow us to change React Native components without needing a reference to them. However, there are some pitfalls that you can fall into while using them:

  • As events can be sent from anywhere, they can introduce spaghetti-style dependencies into your project.
  • Events share namespace, which means that you may encounter some name collisions. Collisions will not be detected statically, what makes them hard to debug.
  • If you use several instances of the same React Native component and you want to distinguish them from the perspective of your event, you'll likely need to introduce some kind of identifiers and pass them along with events (you can use the native view's reactTag as an identifier).

The common pattern we use when embedding native in React Native is to make the native component's RCTViewManager a delegate for the views, sending events back to JavaScript via the bridge. This keeps related event calls in one place.

Calling native functions from React Native (native modules) #

Native modules are Objective-C classes that are available in JS. Typically one instance of each module is created per JS bridge. They can export arbitrary functions and constants to React Native. They have been covered in detail in this article.

The fact that native modules are singletons limits the mechanism in context of embedding. Let's say we have a React Native component embedded in a native view and we want to update the native, parent view. Using the native module mechanism, we would export a function that not only takes expected arguments, but also an identifier of the parent native view. The identifier would be used to retrieve a reference to the parent view to update. That said, we would need to keep a mapping from identifiers to native views in the module.

Although this solution is complex, it is used in RCTUIManager, which is an internal React Native class that manages all React Native views.

Native modules can also be used to expose existing native libraries to JS. Geolocation library is a living example of the idea.

Warning: +All native modules share the same namespace. Watch out for name collisions when creating new ones.

Layout computation flow #

When integrating native and React Native, we also need a way to consolidate two different layout systems. This section covers common layouting problems and provides a brief description of mechanisms that are intended to address them.

Layout of a native component embedded in React Native #

This case is covered in this article. Basically, as all our native react views are subclasses of UIView, most style and size attributes will work like you would expect out of the box.

Layout of a React Native component embedded in native #

React Native content with fixed size #

The simplest scenario is when we have a React Native app with a fixed size, which is known to the native side. In particular, a full-screen React Native view falls into this case. If we want a smaller root view, we can explicitly set RCTRootView's frame.

For instance, to make an RN app 200 (logical) pixels high, and the hosting view's width wide, we could do:

// SomeViewController.m + +- (void)viewDidLoad +{ + [...] + RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge + moduleName:appName + initialProperties:props]; + rootView.frame = CGRectMake(0, 0, self.view.width, 200); + [self.view addSubview:rootView]; +}

When we have a fixed size root view, we need to respect its bounds on the JS side. In other words, we need to ensure that the React Native content can be contained within the fixed-size root view. The easiest way to ensure this is to use flexbox layout. If you use absolute positioning, and React components are visible outside the root view's bounds, you'll get overlap with native views, causing some features to behave unexpectedly. For instance, 'TouchableHighlight' will not highlight your touches outside the root view's bounds.

It's totally fine to update root view's size dynamically by re-setting its frame property. React Native will take care of the content's layout.

React Native content with flexible size #

In some cases we'd like to render content of initially unknown size. Let's say the size will be defined dynamically in JS. We have two solutions to this problem.

  1. You can wrap your React Native view in ScrollView component. This guarantees that your content will always be available and it won't overlap with native views.
  2. React Native allows you to determine, in JS, the size of the RN app and provide it to the owner of the hosting RCTRootView. The owner is then responsible for re-laying out the subviews and keeping the UI consistent. We achieve this with RCTRootView's flexibility modes.

RCTRootView supports 4 different size flexibility modes:

// RCTRootView.h + +typedef NS_ENUM(NSInteger, RCTRootViewSizeFlexibility) { + RCTRootViewSizeFlexibilityNone = 0, + RCTRootViewSizeFlexibilityWidth, + RCTRootViewSizeFlexibilityHeight, + RCTRootViewSizeFlexibilityWidthAndHeight, +};

RCTRootViewSizeFlexibilityNone is the default value, which makes a root view's size fixed (but it still can be updated with setFrame:). The other three modes allow us to track React Native content's size updates. For instance, setting mode to RCTRootViewSizeFlexibilityHeight will cause React Native to measure the content's height and pass that information back to RCTRootView's delegate. An arbitrary action can be performed within the delegate, including setting the root view's frame, so the content fits. The delegate is called only when the size of the content has changed.

Warning: +Making a dimension flexible in both JS and native leads to undefined behavior. For example - don't make a top-level React component's width flexible (with flexbox) while you're using RCTRootViewSizeFlexibilityWidth on the hosting RCTRootView.

Let's look at an example.

// FlexibleSizeExampleView.m + +- (instancetype)initWithFrame:(CGRect)frame +{ + [...] + + _rootView = [[RCTRootView alloc] initWithBridge:bridge + moduleName:@"FlexibilityExampleApp" + initialProperties:@{}]; + + _rootView.delegate = self; + _rootView.sizeFlexibility = RCTRootViewSizeFlexibilityHeight; + _rootView.frame = CGRectMake(0, 0, self.frame.size.width, 0); +} + +#pragma mark - RCTRootViewDelegate +- (void)rootViewDidChangeIntrinsicSize:(RCTRootView *)rootView +{ + CGRect newFrame = rootView.frame; + newFrame.size = rootView.intrinsicSize; + + rootView.frame = newFrame; +}

In the example we have a FlexibleSizeExampleView view that holds a root view. We create the root view, initialize it and set the delegate. The delegate will handle size updates. Then, we set the root view's size flexibility to RCTRootViewSizeFlexibilityHeight, which means that rootViewDidChangeIntrinsicSize: method will be called every time the React Native content changes its height. Finally, we set the root view's width and position. Note that we set there height as well, but it has no effect as we made the height RN-dependent.

You can checkout full source code of the example here.

It's fine to change root view's size flexibility mode dynamically. Changing flexibility mode of a root view will schedule a layout recalculation and the delegate rootViewDidChangeIntrinsicSize: method will be called once the content size is known.

Note: React Native layout calculation is performed on a special thread, while native UI view updates are done on the main thread. This may cause temporary UI inconsistencies between native and React Native. This is a known problem and our team is working on synchronizing UI updates coming from different sources.

Note: React Native does not perform any layout calculations until the root view becomes a subview of some other views. If you want to hide React Native view until its dimensions are known, add the root view as a subview and make it initially hidden (use UIView's hidden property). Then change its visibility in the delegate method.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/datepickerandroid.html b/releases/0.37/docs/datepickerandroid.html new file mode 100644 index 00000000000..8af9e76cf48 --- /dev/null +++ b/releases/0.37/docs/datepickerandroid.html @@ -0,0 +1,136 @@ +DatePickerAndroid

DatePickerAndroid #

Opens the standard Android date picker dialog.

Example #

try { + const {action, year, month, day} = await DatePickerAndroid.open({ + // Use `new Date()` for current date. + // May 25 2020. Month 0 is January. + date: new Date(2020, 4, 25) + }); + if (action !== DatePickerAndroid.dismissedAction) { + // Selected year, month (0-11), day + } +} catch ({code, message}) { + console.warn('Cannot open date picker', message); +}

Methods #

static open(options) #

Opens the standard Android date picker dialog.

The available keys for the options object are: + date (Date object or timestamp in milliseconds) - date to show by default + minDate (Date or timestamp in milliseconds) - minimum date that can be selected + * maxDate (Date object or timestamp in milliseconds) - minimum date that can be selected

Returns a Promise which will be invoked an object containing action, year, month (0-11), +day if the user picked a date. If the user dismissed the dialog, the Promise will +still be resolved with action being DatePickerAndroid.dismissedAction and all the other keys +being undefined. Always check whether the action before reading the values.

Note the native date picker dialog has some UI glitches on Android 4 and lower +when using the minDate and maxDate options.

static dateSetAction(0) #

A date has been selected.

static dismissedAction(0) #

The dialog has been dismissed.

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + DatePickerAndroid, + StyleSheet, + Text, + TouchableWithoutFeedback, +} = ReactNative; + +var UIExplorerBlock = require('./UIExplorerBlock'); +var UIExplorerPage = require('./UIExplorerPage'); + +class DatePickerAndroidExample extends React.Component { + static title = 'DatePickerAndroid'; + static description = 'Standard Android date picker dialog'; + + state = { + presetDate: new Date(2020, 4, 5), + allDate: new Date(2020, 4, 5), + simpleText: 'pick a date', + minText: 'pick a date, no earlier than today', + maxText: 'pick a date, no later than today', + presetText: 'pick a date, preset to 2020/5/5', + allText: 'pick a date between 2020/5/1 and 2020/5/10', + }; + + showPicker = async (stateKey, options) => { + try { + var newState = {}; + const {action, year, month, day} = await DatePickerAndroid.open(options); + if (action === DatePickerAndroid.dismissedAction) { + newState[stateKey + 'Text'] = 'dismissed'; + } else { + var date = new Date(year, month, day); + newState[stateKey + 'Text'] = date.toLocaleDateString(); + newState[stateKey + 'Date'] = date; + } + this.setState(newState); + } catch ({code, message}) { + console.warn(`Error in example '${stateKey}': `, message); + } + }; + + render() { + return ( + <UIExplorerPage title="DatePickerAndroid"> + <UIExplorerBlock title="Simple date picker"> + <TouchableWithoutFeedback + onPress={this.showPicker.bind(this, 'simple', {date: this.state.simpleDate})}> + <Text style={styles.text}>{this.state.simpleText}</Text> + </TouchableWithoutFeedback> + </UIExplorerBlock> + <UIExplorerBlock title="Date picker with pre-set date"> + <TouchableWithoutFeedback + onPress={this.showPicker.bind(this, 'preset', {date: this.state.presetDate})}> + <Text style={styles.text}>{this.state.presetText}</Text> + </TouchableWithoutFeedback> + </UIExplorerBlock> + <UIExplorerBlock title="Date picker with minDate"> + <TouchableWithoutFeedback + onPress={this.showPicker.bind(this, 'min', { + date: this.state.minDate, + minDate: new Date(), + })}> + <Text style={styles.text}>{this.state.minText}</Text> + </TouchableWithoutFeedback> + </UIExplorerBlock> + <UIExplorerBlock title="Date picker with maxDate"> + <TouchableWithoutFeedback + onPress={this.showPicker.bind(this, 'max', { + date: this.state.maxDate, + maxDate: new Date(), + })}> + <Text style={styles.text}>{this.state.maxText}</Text> + </TouchableWithoutFeedback> + </UIExplorerBlock> + <UIExplorerBlock title="Date picker with all options"> + <TouchableWithoutFeedback + onPress={this.showPicker.bind(this, 'all', { + date: this.state.allDate, + minDate: new Date(2020, 4, 1), + maxDate: new Date(2020, 4, 10), + })}> + <Text style={styles.text}>{this.state.allText}</Text> + </TouchableWithoutFeedback> + </UIExplorerBlock> + </UIExplorerPage> + ); + } +} + +var styles = StyleSheet.create({ + text: { + color: 'black', + }, +}); + +module.exports = DatePickerAndroidExample;
\ No newline at end of file diff --git a/releases/0.37/docs/datepickerios.html b/releases/0.37/docs/datepickerios.html new file mode 100644 index 00000000000..3ba2da35fb1 --- /dev/null +++ b/releases/0.37/docs/datepickerios.html @@ -0,0 +1,176 @@ +DatePickerIOS

DatePickerIOS #

Use DatePickerIOS to render a date/time picker (selector) on iOS. This is +a controlled component, so you must hook in to the onDateChange callback +and update the date prop in order for the component to update, otherwise +the user's change will be reverted immediately to reflect props.date as the +source of truth.

Props #

date Date #

The currently selected date.

maximumDate Date #

Maximum date.

Restricts the range of possible date/time values.

minimumDate Date #

Minimum date.

Restricts the range of possible date/time values.

minuteInterval enum(1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30) #

The interval at which minutes can be selected.

mode enum('date', 'time', 'datetime') #

The date picker mode.

onDateChange function #

Date change handler.

This is called when the user changes the date or time in the UI. +The first and only argument is a Date object representing the new +date and time.

timeZoneOffsetInMinutes number #

Timezone offset in minutes.

By default, the date picker will use the device's timezone. With this +parameter, it is possible to force a certain timezone offset. For +instance, to show times in Pacific Standard Time, pass -7 * 60.

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + DatePickerIOS, + StyleSheet, + Text, + TextInput, + View, +} = ReactNative; + +class DatePickerExample extends React.Component { + static defaultProps = { + date: new Date(), + timeZoneOffsetInHours: (-1) * (new Date()).getTimezoneOffset() / 60, + }; + + state = { + date: this.props.date, + timeZoneOffsetInHours: this.props.timeZoneOffsetInHours, + }; + + onDateChange = (date) => { + this.setState({date: date}); + }; + + onTimezoneChange = (event) => { + var offset = parseInt(event.nativeEvent.text, 10); + if (isNaN(offset)) { + return; + } + this.setState({timeZoneOffsetInHours: offset}); + }; + + render() { + // Ideally, the timezone input would be a picker rather than a + // text input, but we don't have any pickers yet :( + return ( + <View> + <WithLabel label="Value:"> + <Text>{ + this.state.date.toLocaleDateString() + + ' ' + + this.state.date.toLocaleTimeString() + }</Text> + </WithLabel> + <WithLabel label="Timezone:"> + <TextInput + onChange={this.onTimezoneChange} + style={styles.textinput} + value={this.state.timeZoneOffsetInHours.toString()} + /> + <Text> hours from UTC</Text> + </WithLabel> + <Heading label="Date + time picker" /> + <DatePickerIOS + date={this.state.date} + mode="datetime" + timeZoneOffsetInMinutes={this.state.timeZoneOffsetInHours * 60} + onDateChange={this.onDateChange} + /> + <Heading label="Date picker" /> + <DatePickerIOS + date={this.state.date} + mode="date" + timeZoneOffsetInMinutes={this.state.timeZoneOffsetInHours * 60} + onDateChange={this.onDateChange} + /> + <Heading label="Time picker, 10-minute interval" /> + <DatePickerIOS + date={this.state.date} + mode="time" + timeZoneOffsetInMinutes={this.state.timeZoneOffsetInHours * 60} + onDateChange={this.onDateChange} + minuteInterval={10} + /> + </View> + ); + } +} + +class WithLabel extends React.Component { + render() { + return ( + <View style={styles.labelContainer}> + <View style={styles.labelView}> + <Text style={styles.label}> + {this.props.label} + </Text> + </View> + {this.props.children} + </View> + ); + } +} + +class Heading extends React.Component { + render() { + return ( + <View style={styles.headingContainer}> + <Text style={styles.heading}> + {this.props.label} + </Text> + </View> + ); + } +} + +exports.displayName = (undefined: ?string); +exports.title = '<DatePickerIOS>'; +exports.description = 'Select dates and times using the native UIDatePicker.'; +exports.examples = [ +{ + title: '<DatePickerIOS>', + render: function(): React.Element<any> { + return <DatePickerExample />; + }, +}]; + +var styles = StyleSheet.create({ + textinput: { + height: 26, + width: 50, + borderWidth: 0.5, + borderColor: '#0f0f0f', + padding: 4, + fontSize: 13, + }, + labelContainer: { + flexDirection: 'row', + alignItems: 'center', + marginVertical: 2, + }, + labelView: { + marginRight: 10, + paddingVertical: 2, + }, + label: { + fontWeight: '500', + }, + headingContainer: { + padding: 4, + backgroundColor: '#f6f7f8', + }, + heading: { + fontWeight: '500', + fontSize: 14, + }, +});
\ No newline at end of file diff --git a/releases/0.37/docs/debugging.html b/releases/0.37/docs/debugging.html new file mode 100644 index 00000000000..8cc92619584 --- /dev/null +++ b/releases/0.37/docs/debugging.html @@ -0,0 +1,37 @@ +Debugging

Debugging #

Accessing the In-App Developer Menu #

You can access the developer menu by shaking your device or by selecting "Shake Gesture" inside the Hardware menu in the iOS Simulator. You can also use the Command + D keyboard shortcut when your app is running in the iPhone Simulator, or Command + M when running in an Android emulator.

The Developer Menu is disabled in release (production) builds.

Reloading JavaScript #

Instead of recompiling your app every time you make a change, you can reload your app's JavaScript code instantly. To do so, select "Reload" from the Developer Menu. You can also press Command + R in the iOS Simulator, or press R twice on Android emulators.

If the Command + R keyboard shortcut does not seem to reload the iOS Simulator, go to the Hardware menu, select Keyboard, and make sure that "Connect Hardware Keyboard" is checked.

Automatic reloading #

You can speed up your development times by having your app reload automatically any time your code changes. Automatic reloading can be enabled by selecting "Enable Live Reload" from the Developer Menu.

You may even go a step further and keep your app running as new versions of your files are injected into the JavaScript bundle automatically by enabling Hot Reloading from the Developer Menu. This will allow you to persist the app's state through reloads.

There are some instances where hot reloading cannot be implemented perfectly. If you run into any issues, use a full reload to reset your app.

You will need to rebuild your app for changes to take effect in certain situations:

  • You have added new resources to your native app's bundle, such as an image in Images.xcassets on iOS or the res/drawable folder on Android.
  • You have modified native code (Objective-C/Swift on iOS or Java/C++ on Android).

In-app Errors and Warnings #

Errors and warnings are displayed inside your app in development builds.

Errors #

In-app errors are displayed in a full screen alert with a red background inside your app. This screen is known as a RedBox. You can use console.error() to manually trigger one.

Warnings #

Warnings will be displayed on screen with a yellow background. These alerts are known as YellowBoxes. Click on the alerts to show more information or to dismiss them.

As with a RedBox, you can use console.warn() to trigger a YellowBox.

YellowBoxes can be disabled during development by using console.disableYellowBox = true;. Specific warnings can be ignored programmatically by setting an array of prefixes that should be ignored: console.ignoredYellowBox = ['Warning: ...'];

RedBoxes and YellowBoxes are automatically disabled in release (production) builds.

Accessing console logs #

You can display the console logs for an iOS or Android app by using the following commands in a terminal while the app is running:

$ react-native log-ios +$ react-native log-android

You may also access these through Debug → Open System Log... in the iOS Simulator or by running adb logcat *:S ReactNative:V ReactNativeJS:V in a terminal while an Android app is running on a device or emulator.

Chrome Developer Tools #

To debug the JavaScript code in Chrome, select "Debug JS Remotely" from the Developer Menu. This will open a new tab at http://localhost:8081/debugger-ui.

Select Tools → Developer Tools from the Chrome Menu to open the Developer Tools. You may also access the DevTools using keyboard shortcuts (Command + Option + I on Mac, Ctrl + Shift + I on Windows). You may also want to enable Pause On Caught Exceptions for a better debugging experience.

It is currently not possible to use the "React" tab in the Chrome Developer Tools to inspect app widgets. You can use Nuclide's "React Native Inspector" as a workaround.

Debugging on a device with Chrome Developer Tools #

On iOS devices, open the file RCTWebSocketExecutor.m and change "localhost" to the IP address of your computer, then select "Debug JS Remotely" from the Developer Menu.

On Android 5.0+ devices connected via USB, you can use the adb command line tool to setup port forwarding from the device to your computer:

adb reverse tcp:8081 tcp:8081

Alternatively, select "Dev Settings" from the Developer Menu, then update the "Debug server host for device" setting to match the IP address of your computer.

If you run into any issues, it may be possible that one of your Chrome extensions is interacting in unexpected ways with the debugger. Try disabling all of your extensions and re-enabling them one-by-one until you find the problematic extension.

Debugging using a custom JavaScript debugger #

To use a custom JavaScript debugger in place of Chrome Developer Tools, set the REACT_DEBUGGER environment variable to a command that will start your custom debugger. You can then select "Debug JS Remotely" from the Developer Menu to start debugging.

The debugger will receive a list of all project roots, separated by a space. For example, if you set REACT_DEBUGGER="node /path/to/launchDebugger.js --port 2345 --type ReactNative", then the command node /path/to/launchDebugger.js --port 2345 --type ReactNative /path/to/reactNative/app will be used to start your debugger.

Custom debugger commands executed this way should be short-lived processes, and they shouldn't produce more than 200 kilobytes of output.

Debugging with Stetho on Android #

  1. In android/app/build.gradle, add these lines in the dependencies section:

    compile 'com.facebook.stetho:stetho:1.3.1' +compile 'com.facebook.stetho:stetho-okhttp3:1.3.1'
  2. In android/app/src/main/java/com/{yourAppName}/MainApplication.java, add the following imports:

    import com.facebook.react.modules.network.ReactCookieJarContainer; +import com.facebook.stetho.Stetho; +import okhttp3.OkHttpClient; +import com.facebook.react.modules.network.OkHttpClientProvider; +import com.facebook.stetho.okhttp3.StethoInterceptor; +import java.util.concurrent.TimeUnit;
  3. In android/app/src/main/java/com/{yourAppName}/MainApplication.java add the function:

    public void onCreate() { + super.onCreate(); + Stetho.initializeWithDefaults(this); + OkHttpClient client = new OkHttpClient.Builder() + .connectTimeout(0, TimeUnit.MILLISECONDS) + .readTimeout(0, TimeUnit.MILLISECONDS) + .writeTimeout(0, TimeUnit.MILLISECONDS) + .cookieJar(new ReactCookieJarContainer()) + .addNetworkInterceptor(new StethoInterceptor()) + .build(); + OkHttpClientProvider.replaceOkHttpClient(client); +}
  4. Run react-native run-android

  5. In a new chrome tab, open : chrome://inspect, click on 'Inspect device' (the one followed by "Powered by Stetho")

Debugging native code #

When working with native code (e.g. when writing native modules) you can launch the app from Android Studio or Xcode and take advantage of the debugging features (setup breakpoints, etc.) as you would in case of building a standard native app.

Performance Monitor #

You can enable a performance overlay to help you debug performance problems by selecting "Perf Monitor" in the Developer Menu.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/dimensions.html b/releases/0.37/docs/dimensions.html new file mode 100644 index 00000000000..82b43a0e6c0 --- /dev/null +++ b/releases/0.37/docs/dimensions.html @@ -0,0 +1,26 @@ +Dimensions

Dimensions #

Methods #

static set(dims) #

This should only be called from native code by sending the +didUpdateDimensions event.

@param {object} dims Simple string-keyed object of dimensions to set

static get(dim) #

Initial dimensions are set before runApplication is called so they should +be available before any other require's are run, but may be updated later.

Note: Although dimensions are available immediately, they may change (e.g +due to device rotation) so any rendering logic or styles that depend on +these constants should try to call this function on every render, rather +than caching the value (for example, using inline styles rather than +setting a value in a StyleSheet).

Example: var {height, width} = Dimensions.get('window');

@param {string} dim Name of dimension as defined when calling set. +@returns {Object?} Value for the dimension.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/direct-manipulation.html b/releases/0.37/docs/direct-manipulation.html new file mode 100644 index 00000000000..a3820f340e8 --- /dev/null +++ b/releases/0.37/docs/direct-manipulation.html @@ -0,0 +1,157 @@ +Direct Manipulation

Direct Manipulation #

It is sometimes necessary to make changes directly to a component +without using state/props to trigger a re-render of the entire subtree. +When using React in the browser for example, you sometimes need to +directly modify a DOM node, and the same is true for views in mobile +apps. setNativeProps is the React Native equivalent to setting +properties directly on a DOM node.

Use setNativeProps when frequent re-rendering creates a performance bottleneck

Direct manipulation will not be a tool that you reach for +frequently; you will typically only be using it for creating +continuous animations to avoid the overhead of rendering the component +hierarchy and reconciling many views. setNativeProps is imperative +and stores state in the native layer (DOM, UIView, etc.) and not +within your React components, which makes your code more difficult to +reason about. Before you use it, try to solve your problem with setState +and shouldComponentUpdate.

setNativeProps with TouchableOpacity #

TouchableOpacity +uses setNativeProps internally to update the opacity of its child +component:

setOpacityTo(value) { + // Redacted: animation related code + this.refs[CHILD_REF].setNativeProps({ + opacity: value + }); +},

This allows us to write the following code and know that the child will +have its opacity updated in response to taps, without the child having +any knowledge of that fact or requiring any changes to its implementation:

<TouchableOpacity onPress={this._handlePress}> + <View style={styles.button}> + <Text>Press me!</Text> + </View> +</TouchableOpacity>

Let's imagine that setNativeProps was not available. One way that we +might implement it with that constraint is to store the opacity value +in the state, then update that value whenever onPress is fired:

constructor(props) { + super(props); + this.state = { myButtonOpacity: 1, }; +} + +render() { + return ( + <TouchableOpacity onPress={() => this.setState({myButtonOpacity: 0.5})} + onPressOut={() => this.setState({myButtonOpacity: 1})}> + <View style={[styles.button, {opacity: this.state.myButtonOpacity}]}> + <Text>Press me!</Text> + </View> + </TouchableOpacity> + ) +}

This is computationally intensive compared to the original example - +React needs to re-render the component hierarchy each time the opacity +changes, even though other properties of the view and its children +haven't changed. Usually this overhead isn't a concern but when +performing continuous animations and responding to gestures, judiciously +optimizing your components can improve your animations' fidelity.

If you look at the implementation of setNativeProps in +NativeMethodsMixin.js +you will notice that it is a wrapper around RCTUIManager.updateView - +this is the exact same function call that results from re-rendering - +see receiveComponent in +ReactNativeBaseComponent.js.

Composite components and setNativeProps #

Composite components are not backed by a native view, so you cannot call +setNativeProps on them. Consider this example:

class MyButton extends React.Component { + render() { + return ( + <View> + <Text>{this.props.label}</Text> + </View> + ) + } +} + +class App extends React.Component { + render() { + return ( + <TouchableOpacity> + <MyButton label="Press me!" /> + </TouchableOpacity> + ) + } +}

Run this example

If you run this you will immediately see this error: Touchable child +must either be native or forward setNativeProps to a native component. +This occurs because MyButton isn't directly backed by a native view +whose opacity should be set. You can think about it like this: if you +define a component with React.createClass you would not expect to be +able to set a style prop on it and have that work - you would need to +pass the style prop down to a child, unless you are wrapping a native +component. Similarly, we are going to forward setNativeProps to a +native-backed child component.

Forward setNativeProps to a child #

All we need to do is provide a setNativeProps method on our component +that calls setNativeProps on the appropriate child with the given +arguments.

class MyButton extends React.Component { + setNativeProps(nativeProps) { + this._root.setNativeProps(nativeProps); + } + + render() { + return ( + <View ref={component => this._root = component} {...this.props}> + <Text>{this.props.label}</Text> + </View> + ) + } +}

Run this example

You can now use MyButton inside of TouchableOpacity! A sidenote for +clarity: we used the ref callback syntax here, rather than the traditional string-based ref.

You may have noticed that we passed all of the props down to the child +view using {...this.props}. The reason for this is that +TouchableOpacity is actually a composite component, and so in addition +to depending on setNativeProps on its child, it also requires that the +child perform touch handling. To do this, it passes on various +props +that call back to the TouchableOpacity component. +TouchableHighlight, in contrast, is backed by a native view and only +requires that we implement setNativeProps.

setNativeProps to clear TextInput value #

Another very common use case of setNativeProps is to clear the value +of a TextInput. The controlled prop of TextInput can sometimes drop +characters when the bufferDelay is low and the user types very +quickly. Some developers prefer to skip this prop entirely and instead +use setNativeProps to directly manipulate the TextInput value when +necessary. For example, the following code demonstrates clearing the +input when you tap a button:

class App extends React.Component { + constructor(props) { + super(props); + this.clearText = this.clearText.bind(this); + } + + clearText() { + this._textInput.setNativeProps({text: ''}); + } + + render() { + return ( + <View style={styles.container}> + <TextInput ref={component => this._textInput = component} + style={styles.textInput} /> + <TouchableOpacity onPress={this.clearText}> + <Text>Clear text</Text> + </TouchableOpacity> + </View> + ); + } +}

Run this example

Avoiding conflicts with the render function #

If you update a property that is also managed by the render function, +you might end up with some unpredictable and confusing bugs because +anytime the component re-renders and that property changes, whatever +value was previously set from setNativeProps will be completely +ignored and overridden. See this example +for a demonstration of what can happen if these two collide - notice +the jerky animation each 250ms when setState triggers a re-render.

setNativeProps & shouldComponentUpdate #

By intelligently applying +shouldComponentUpdate +you can avoid the unnecessary overhead involved in reconciling unchanged +component subtrees, to the point where it may be performant enough to +use setState instead of setNativeProps.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/drawerlayoutandroid.html b/releases/0.37/docs/drawerlayoutandroid.html new file mode 100644 index 00000000000..3baf94a4fa3 --- /dev/null +++ b/releases/0.37/docs/drawerlayoutandroid.html @@ -0,0 +1,58 @@ +DrawerLayoutAndroid

DrawerLayoutAndroid #

React component that wraps the platform DrawerLayout (Android only). The +Drawer (typically used for navigation) is rendered with renderNavigationView +and direct children are the main view (where your content goes). The navigation +view is initially not visible on the screen, but can be pulled in from the +side of the window specified by the drawerPosition prop and its width can +be set by the drawerWidth prop.

Example:

render: function() { + var navigationView = ( + <View style={{flex: 1, backgroundColor: '#fff'}}> + <Text style={{margin: 10, fontSize: 15, textAlign: 'left'}}>I'm in the Drawer!</Text> + </View> + ); + return ( + <DrawerLayoutAndroid + drawerWidth={300} + drawerPosition={DrawerLayoutAndroid.positions.Left} + renderNavigationView={() => navigationView}> + <View style={{flex: 1, alignItems: 'center'}}> + <Text style={{margin: 10, fontSize: 15, textAlign: 'right'}}>Hello</Text> + <Text style={{margin: 10, fontSize: 15, textAlign: 'right'}}>World!</Text> + </View> + </DrawerLayoutAndroid> + ); +},

Props #

drawerBackgroundColor color #

Specifies the background color of the drawer. The default value is white. +If you want to set the opacity of the drawer, use rgba. Example:

return ( + <DrawerLayoutAndroid drawerBackgroundColor="rgba(0,0,0,0.5)"> + </DrawerLayoutAndroid> +);

drawerLockMode enum('unlocked', 'locked-closed', 'locked-open') #

Specifies the lock mode of the drawer. The drawer can be locked in 3 states: +- unlocked (default), meaning that the drawer will respond (open/close) to touch gestures. +- locked-closed, meaning that the drawer will stay closed and not respond to gestures. +- locked-open, meaning that the drawer will stay opened and not respond to gestures. +The drawer may still be opened and closed programmatically (openDrawer/closeDrawer).

drawerPosition enum(DrawerConsts.DrawerPosition.Left, DrawerConsts.DrawerPosition.Right) #

Specifies the side of the screen from which the drawer will slide in.

drawerWidth number #

Specifies the width of the drawer, more precisely the width of the view that be pulled in +from the edge of the window.

keyboardDismissMode enum('none', 'on-drag') #

Determines whether the keyboard gets dismissed in response to a drag. + - 'none' (the default), drags do not dismiss the keyboard. + - 'on-drag', the keyboard is dismissed when a drag begins.

onDrawerClose function #

Function called whenever the navigation view has been closed.

onDrawerOpen function #

Function called whenever the navigation view has been opened.

onDrawerSlide function #

Function called whenever there is an interaction with the navigation view.

onDrawerStateChanged function #

Function called when the drawer state has changed. The drawer can be in 3 states: +- idle, meaning there is no interaction with the navigation view happening at the time +- dragging, meaning there is currently an interaction with the navigation view +- settling, meaning that there was an interaction with the navigation view, and the +navigation view is now finishing its closing or opening animation

renderNavigationView function #

The navigation view that will be rendered to the side of the screen and can be pulled in.

statusBarBackgroundColor color #

Make the drawer take the entire screen and draw the background of the +status bar to allow it to open over the status bar. It will only have an +effect on API 21+.

Methods #

openDrawer(0) #

Opens the drawer.

closeDrawer(0) #

Closes the drawer.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/easing.html b/releases/0.37/docs/easing.html new file mode 100644 index 00000000000..9599848ce04 --- /dev/null +++ b/releases/0.37/docs/easing.html @@ -0,0 +1,24 @@ +Easing

Easing #

This class implements common easing functions. The math is pretty obscure, +but this cool website has nice visual illustrations of what they represent: +http://xaedes.de/dev/transitions/

Methods #

static step0(n) #

static step1(n) #

static linear(t) #

static ease(t) #

static quad(t) #

static cubic(t) #

static poly(n) #

static sin(t) #

static circle(t) #

static exp(t) #

static elastic(bounciness) #

A simple elastic interaction, similar to a spring. Default bounciness +is 1, which overshoots a little bit once. 0 bounciness doesn't overshoot +at all, and bounciness of N > 1 will overshoot about N times.

Wolfram Plots:

http://tiny.cc/elastic_b_1 (default bounciness = 1) + http://tiny.cc/elastic_b_3 (bounciness = 3)

static back(s) #

static bounce(t) #

static bezier(x1, y1, x2, y2) #

static in(easing) #

static out(easing) #

Runs an easing function backwards.

static inOut(easing) #

Makes any easing function symmetrical.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/flexbox.html b/releases/0.37/docs/flexbox.html new file mode 100644 index 00000000000..959cd085e26 --- /dev/null +++ b/releases/0.37/docs/flexbox.html @@ -0,0 +1,79 @@ +Layout with Flexbox

Layout with Flexbox #

A component can specify the layout of its children using the flexbox algorithm. Flexbox is designed to provide a consistent layout on different screen sizes.

You will normally use a combination of flexDirection, alignItems, and justifyContent to achieve the right layout.

Flexbox works the same way in React Native as it does in CSS on the web, with a few exceptions. The defaults are different, with flexDirection defaulting to column instead of row, and alignItems defaulting to stretch instead of flex-start, and the flex parameter only supports a single number.

Flex Direction #

Adding flexDirection to a component's style determines the primary axis of its layout. Should the children be organized horizontally (row) or vertically (column)? The default is column.

import React, { Component } from 'react'; +import { AppRegistry, View } from 'react-native'; + +class FlexDirectionBasics extends Component { + render() { + return ( + // Try setting `flexDirection` to `column`. + <View style={{flex: 1, flexDirection: 'row'}}> + <View style={{width: 50, height: 50, backgroundColor: 'powderblue'}} /> + <View style={{width: 50, height: 50, backgroundColor: 'skyblue'}} /> + <View style={{width: 50, height: 50, backgroundColor: 'steelblue'}} /> + </View> + ); + } +}; + +AppRegistry.registerComponent('AwesomeProject', () => FlexDirectionBasics);

Justify Content #

Adding justifyContent to a component's style determines the distribution of children along the primary axis. Should children be distributed at the start, the center, the end, or spaced evenly? Available options are flex-start, center, flex-end, space-around, and space-between.

import React, { Component } from 'react'; +import { AppRegistry, View } from 'react-native'; + +class JustifyContentBasics extends Component { + render() { + return ( + // Try setting `justifyContent` to `center`. + // Try setting `flexDirection` to `row`. + <View style={{ + flex: 1, + flexDirection: 'column', + justifyContent: 'space-between', + }}> + <View style={{width: 50, height: 50, backgroundColor: 'powderblue'}} /> + <View style={{width: 50, height: 50, backgroundColor: 'skyblue'}} /> + <View style={{width: 50, height: 50, backgroundColor: 'steelblue'}} /> + </View> + ); + } +}; + +AppRegistry.registerComponent('AwesomeProject', () => JustifyContentBasics);

Align Items #

Adding alignItems to a component's style determines the alignment of children along the secondary axis (if the primary axis is row, then the secondary is column, and vice versa). Should children be aligned at the start, the center, the end, or stretched to fill? Available options are flex-start, center, flex-end, and stretch.

For stretch to have an effect, children must not have a fixed dimension along the secondary axis. In the following example, setting alignItems: stretch does nothing until the width: 50 is removed from the children.

import React, { Component } from 'react'; +import { AppRegistry, View } from 'react-native'; + +class AlignItemsBasics extends Component { + render() { + return ( + // Try setting `alignItems` to 'flex-start' + // Try setting `justifyContent` to `flex-end`. + // Try setting `flexDirection` to `row`. + <View style={{ + flex: 1, + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + }}> + <View style={{width: 50, height: 50, backgroundColor: 'powderblue'}} /> + <View style={{width: 50, height: 50, backgroundColor: 'skyblue'}} /> + <View style={{width: 50, height: 50, backgroundColor: 'steelblue'}} /> + </View> + ); + } +}; + +AppRegistry.registerComponent('AwesomeProject', () => AlignItemsBasics);

Going Deeper #

We've covered the basics, but there are many other styles you may need for layouts. The full list of props that control layout is documented here.

We're getting close to being able to build a real application. One thing we are still missing is a way to take user input, so let's move on to learn how to handle text input with the TextInput component.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/geolocation.html b/releases/0.37/docs/geolocation.html new file mode 100644 index 00000000000..2ed7287d979 --- /dev/null +++ b/releases/0.37/docs/geolocation.html @@ -0,0 +1,101 @@ +Geolocation

Geolocation #

The Geolocation API extends the web spec: +https://developer.mozilla.org/en-US/docs/Web/API/Geolocation

As a browser polyfill, this API is available through the navigator.geolocation +global - you do not need to import it.

iOS #

You need to include the NSLocationWhenInUseUsageDescription key +in Info.plist to enable geolocation. Geolocation is enabled by default +when you create a project with react-native init.

Android #

To request access to location, you need to add the following line to your +app's AndroidManifest.xml:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

Android API >= 18 Positions will also contain a mocked boolean to indicate if position +was created from a mock provider.

Methods #

static getCurrentPosition(geo_success, geo_error?, geo_options?) #

Invokes the success callback once with the latest location info. Supported +options: timeout (ms), maximumAge (ms), enableHighAccuracy (bool) +On Android, this can return almost immediately if the location is cached or +request an update, which might take a while.

static watchPosition(success, error?, options?) #

Invokes the success callback whenever the location changes. Supported +options: timeout (ms), maximumAge (ms), enableHighAccuracy (bool), distanceFilter(m)

static clearWatch(watchID) #

static stopObserving(0) #

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
/* eslint no-console: 0 */ +'use strict'; + + +var React = require('react'); +var ReactNative = require('react-native'); +var { + StyleSheet, + Text, + View, +} = ReactNative; + +exports.framework = 'React'; +exports.title = 'Geolocation'; +exports.description = 'Examples of using the Geolocation API.'; + +exports.examples = [ + { + title: 'navigator.geolocation', + render: function(): React.Element<any> { + return <GeolocationExample />; + }, + } +]; + +class GeolocationExample extends React.Component { + state = { + initialPosition: 'unknown', + lastPosition: 'unknown', + }; + + watchID: ?number = null; + + componentDidMount() { + navigator.geolocation.getCurrentPosition( + (position) => { + var initialPosition = JSON.stringify(position); + this.setState({initialPosition}); + }, + (error) => alert(JSON.stringify(error)), + {enableHighAccuracy: true, timeout: 20000, maximumAge: 1000} + ); + this.watchID = navigator.geolocation.watchPosition((position) => { + var lastPosition = JSON.stringify(position); + this.setState({lastPosition}); + }); + } + + componentWillUnmount() { + navigator.geolocation.clearWatch(this.watchID); + } + + render() { + return ( + <View> + <Text> + <Text style={styles.title}>Initial position: </Text> + {this.state.initialPosition} + </Text> + <Text> + <Text style={styles.title}>Current position: </Text> + {this.state.lastPosition} + </Text> + </View> + ); + } +} + +var styles = StyleSheet.create({ + title: { + fontWeight: '500', + }, +});
\ No newline at end of file diff --git a/releases/0.37/docs/gesture-responder-system.html b/releases/0.37/docs/gesture-responder-system.html new file mode 100644 index 00000000000..7c56581d3a4 --- /dev/null +++ b/releases/0.37/docs/gesture-responder-system.html @@ -0,0 +1,19 @@ +Gesture Responder System

Gesture Responder System #

Gesture recognition on mobile devices is much more complicated than web. A touch can go through several phases as the app determines what the user's intention is. For example, the app needs to determine if the touch is scrolling, sliding on a widget, or tapping. This can even change during the duration of a touch. There can also be multiple simultaneous touches.

The touch responder system is needed to allow components to negotiate these touch interactions without any additional knowledge about their parent or child components. This system is implemented in ResponderEventPlugin.js, which contains further details and documentation.

Best Practices #

Users can feel huge differences in the usability of web apps vs. native, and this is one of the big causes. Every action should have the following attributes:

  • Feedback/highlighting- show the user what is handling their touch, and what will happen when they release the gesture
  • Cancel-ability- when making an action, the user should be able to abort it mid-touch by dragging their finger away

These features make users more comfortable while using an app, because it allows people to experiment and interact without fear of making mistakes.

TouchableHighlight and Touchable* #

The responder system can be complicated to use. So we have provided an abstract Touchable implementation for things that should be "tappable". This uses the responder system and allows you to easily configure tap interactions declaratively. Use TouchableHighlight anywhere where you would use a button or link on web.

Responder Lifecycle #

A view can become the touch responder by implementing the correct negotiation methods. There are two methods to ask the view if it wants to become responder:

  • View.props.onStartShouldSetResponder: (evt) => true, - Does this view want to become responder on the start of a touch?
  • View.props.onMoveShouldSetResponder: (evt) => true, - Called for every touch move on the View when it is not the responder: does this view want to "claim" touch responsiveness?

If the View returns true and attempts to become the responder, one of the following will happen:

  • View.props.onResponderGrant: (evt) => {} - The View is now responding for touch events. This is the time to highlight and show the user what is happening
  • View.props.onResponderReject: (evt) => {} - Something else is the responder right now and will not release it

If the view is responding, the following handlers can be called:

  • View.props.onResponderMove: (evt) => {} - The user is moving their finger
  • View.props.onResponderRelease: (evt) => {} - Fired at the end of the touch, ie "touchUp"
  • View.props.onResponderTerminationRequest: (evt) => true - Something else wants to become responder. Should this view release the responder? Returning true allows release
  • View.props.onResponderTerminate: (evt) => {} - The responder has been taken from the View. Might be taken by other views after a call to onResponderTerminationRequest, or might be taken by the OS without asking (happens with control center/ notification center on iOS)

evt is a synthetic touch event with the following form:

  • nativeEvent
    • changedTouches - Array of all touch events that have changed since the last event
    • identifier - The ID of the touch
    • locationX - The X position of the touch, relative to the element
    • locationY - The Y position of the touch, relative to the element
    • pageX - The X position of the touch, relative to the root element
    • pageY - The Y position of the touch, relative to the root element
    • target - The node id of the element receiving the touch event
    • timeStamp - A time identifier for the touch, useful for velocity calculation
    • touches - Array of all current touches on the screen

Capture ShouldSet Handlers #

onStartShouldSetResponder and onMoveShouldSetResponder are called with a bubbling pattern, where the deepest node is called first. That means that the deepest component will become responder when multiple Views return true for *ShouldSetResponder handlers. This is desirable in most cases, because it makes sure all controls and buttons are usable.

However, sometimes a parent will want to make sure that it becomes responder. This can be handled by using the capture phase. Before the responder system bubbles up from the deepest component, it will do a capture phase, firing on*ShouldSetResponderCapture. So if a parent View wants to prevent the child from becoming responder on a touch start, it should have a onStartShouldSetResponderCapture handler which returns true.

  • View.props.onStartShouldSetResponderCapture: (evt) => true,
  • View.props.onMoveShouldSetResponderCapture: (evt) => true,

PanResponder #

For higher-level gesture interpretation, check out PanResponder.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/getting-started.html b/releases/0.37/docs/getting-started.html new file mode 100644 index 00000000000..9f10919a8f9 --- /dev/null +++ b/releases/0.37/docs/getting-started.html @@ -0,0 +1,220 @@ +Getting Started

Getting Started #

Welcome to React Native! This page will help you install React Native on +your system, so that you can build apps with it right away. If you already +have React Native installed, you can skip ahead to the +Tutorial.

The instructions are a bit different depending on your development operating system, and whether you want to start developing for iOS or Android. If you +want to develop for both iOS and Android, that's fine - you just have to pick +one to start with, since the setup is a bit different.

+ +Mobile OS: +iOS +Android +Development OS: +Mac +Linux +Windows +
+ +
+ + + +

Unsupported #

Unfortunately, Apple only lets you develop for iOS on a Mac. If you want to build an iOS app but you don't have a Mac yet, you can try starting with the Android instructions instead.
+ +
+ +
+ + + +

Installing Dependencies #

+ +

You will need Node.js, Watchman, the React Native command line interface, and Xcode.

+ +

You will need Node.js, Watchman, the React Native command line interface, and Android Studio.

+ +

Node, Watchman #

We recommend installing Node and Watchman using Homebrew. Run the following commands in a Terminal after installing Homebrew:

brew install node +brew install watchman

Watchman is a tool by Facebook for watching +changes in the filesystem. It is highly recommended you install it for better performance.

The React Native CLI #

Node.js comes with npm, which lets you install the React Native command line interface. Run the following command in a Terminal:

npm install -g react-native-cli

If you get a permission error, try using sudo: sudo npm install -g react-native-cli.

If you get an error like Cannot find module 'npmlog', try installing npm directly: curl -0 -L http://npmjs.org/install.sh | sudo sh.

+ +

Xcode #

The easiest way to install Xcode is via the Mac App Store. Installing Xcode will also install the iOS Simulator and all the necessary tools to build your iOS app.

+ +

Android Development Environment #

Setting up your development environment can be somewhat tedious if you're new to Android development. If you're already familiar with Android development, there are a few things you may need to configure. In either case, please make sure to carefully follow the next few steps.

1. Install Android Studio #

Download and install Android Studio.

2. Confirm the Android SDK is installed #

Android Studio installs Android 7.0 (Nougat) by default. You can confirm that the SDK was installed by clicking on "Configure" in the last screen in the Android Studio Setup Wizard, or by opening "Preferences" from the Android Studio menu, then choosing Appearance and BehaviorSystem SettingsAndroid SDK.

Android Studio SDK Manager

Select "SDK Platforms" from within the SDK Manager and you should see a blue checkmark next to "Android 7.0 (Nougat)". In case it is not, click on the checkbox and then "Apply".

Android Studio SDK Manager

If you wish to support older versions of Android, you can install additional Android SDKs from this screen.

3. Set up paths #

The React Native command line interface requires the ANDROID_HOME environment variable to be set up. You can configure it in a Terminal using the following command:

export ANDROID_HOME=~/Library/Android/sdk

To avoid doing this every time you open a new Terminal, create (or edit) ~/.bashrc using your favorite text editor and add the following lines:

export ANDROID_HOME=~/Library/Android/sdk +export PATH=${PATH}:${ANDROID_HOME}/tools

The second line will add the android tool to your path, which will come in handy in the next step.

Please make sure you export the correct path for ANDROID_HOME if you did not install the Android SDK using Android Studio. If you install the Android SDK using Homebrew, it will be located at /usr/local/opt/android-sdk.

4. Set up your Android Virtual Device #

Android Studio should have set up an Android Virtual Device for you during installation, but it is very common to run into an issue where Android Studio fails to install the AVD.

Android Studio AVD Manager

To see the list of available AVDs, launch the "AVD Manager" from within Android Studio or run the following command in a Terminal:

android avd

You may follow the Android Studio User Guide to create a new AVD if needed.

If you see "No system images installed for this target." under CPU/ABI, go back to your "SDK Manager" and click on "Show Package Details" under "SDK Platforms". You will then be able to install any missing system images, such as "Google APIs Intel Atom (x86)".

+ + + +

Installing Dependencies #

+ +

You will need Node.js, the React Native command line interface, and Android Studio.

Node #

Follow the installation instructions for your Linux distribution to install Node.js 4 or newer.

+ +

You will need Node.js, the React Native command line interface, and Android Studio.

Node #

We recommend installing Node.js and Python2 via Chocolatey, a popular package manager for Windows. Open a Command Prompt as Administrator, then run:

choco install nodejs.install +choco install python2

You can find additional installation options on Node.js's Downloads page.

+ +

The React Native CLI #

Node comes with npm, which lets you install the React Native command line interface.

npm install -g react-native-cli

Android Development Environment #

Setting up your development environment can be somewhat tedious if you're new to Android development. If you're already familiar with Android development, there are a few things you may need to configure. In either case, please make sure to carefully follow the next few steps.

1. Install Android Studio #

Download and install Android Studio.

2. Confirm the Android SDK is installed #

Android Studio installs Android 7.0 (Nougat) by default. You can confirm that the SDK was installed by clicking on "Configure" in the last screen in the Android Studio Setup Wizard, or by opening "Preferences" from the Android Studio menu, then choosing Appearance and BehaviorSystem SettingsAndroid SDK.

Android Studio SDK Manager

Select "SDK Platforms" from within the SDK Manager and you should see a blue checkmark next to "Android 7.0 (Nougat)". In case it is not, click on the checkbox and then "Apply".

Android Studio SDK Manager

If you wish to support older versions of Android, you can install additional Android SDKs from this screen.

3. Set up paths #

The React Native command line interface requires the ANDROID_HOME environment variable to be set up.

+ +

Create or edit your ~/.bashrc file and add the following lines:

export ANDROID_HOME=~/Android/Sdk +export PATH=${PATH}:${ANDROID_HOME}/tools

The second line will add the android tool to your path, which will come in handy in the next step.

Please make sure you export the correct path for ANDROID_HOME if you did not install the Android SDK using Android Studio.

+ +

Go to Control PanelSystem and SecuritySystemChange settings → +Advanced System SettingsEnvironment variablesNew, then enter the path to your Android SDK.

env variable

Please make sure you use the correct path for ANDROID_HOME if you did not install the Android SDK using Android Studio.

Restart the Command Prompt to apply the new environment variable.

+ +

4. Set up your Android Virtual Device #

Android Studio should have set up an Android Virtual Device for you during installation, but it is very common to run into an issue where Android Studio fails to install the AVD.

Android Studio AVD Manager

To see the list of available AVDs, launch the "AVD Manager" from within Android Studio or run the following command in a terminal:

android avd

You may follow the Android Studio User Guide to create a new AVD if needed.

If you see "No system images installed for this target." under CPU/ABI, go back to your "SDK Manager" and click on "Show Package Details" under "SDK Platforms". You will then be able to install any missing system images, such as "Google APIs Intel Atom (x86)".

+ +

Watchman (optional) #

Follow the Watchman installation guide to compile and install Watchman from source.

Watchman is a tool by Facebook for watching +changes in the filesystem. It is highly recommended you install it for better performance, but it's alright to skip this if you find the process to be tedious.

+ +

Testing your React Native Installation #

+ +

Use the React Native command line interface to generate a new React Native project called "AwesomeProject", then run react-native run-ios inside the newly created folder.

react-native init AwesomeProject +cd AwesomeProject +react-native run-ios

You should see your new app running in the iOS Simulator shortly.

react-native run-ios is just one way to run your app. You can also run it directly from within Xcode or Nuclide.

+ +

Use the React Native command line interface to generate a new React Native project called "AwesomeProject", then run react-native run-android inside the newly created folder.

react-native init AwesomeProject +cd AwesomeProject +react-native run-android

If everything is set up correctly, you should see your new app running in your Android emulator shortly. react-native run-android is just one way to run your app - you can also run it directly from within Android Studio or Nuclide.

+ +

Modifying your app #

Now that you have successfully run the app, let's modify it.

+ +
  • Open index.ios.js in your text editor of choice and edit some lines.
  • Hit Command⌘ + R in your iOS Simulator to reload the app and see your change!
+ +
  • Open index.android.js in your text editor of choice and edit some lines.
  • Press the R key twice or select Reload from the Developer Menu to see your change!
+ +

That's it! #

Congratulations! You've successfully run and modified your first React Native app.

+ +
+ +

Testing your React Native Installation #

Use the React Native command line interface to generate a new React Native project called "AwesomeProject", then run react-native run-android inside the newly created folder.

react-native init AwesomeProject +cd AwesomeProject +react-native run-android

If everything is set up correctly, you should see your new app running in your Android emulator shortly.

A common issue is that the packager is not started automatically when you run +react-native run-android. You can start it manually using react-native start.

+ +

If you hit a ERROR Watcher took too long to load on Windows, try increasing the timeout in this file (under your node_modules/react-native/).

+ +

Modifying your app #

Now that you have successfully run the app, let's modify it.

  • Open index.android.js in your text editor of choice and edit some lines.
  • Press the R key twice or select Reload from the Developer Menu to see your change!

That's it! #

Congratulations! You've successfully run and modified a React Native app.

+ +
+ +

Now What? #

  • If you want to add this new React Native code to an existing application, check out the Integration guide.

  • If you can't get this to work, see the Troubleshooting page.

  • If you're curious to learn more about React Native, continue on +to the Tutorial.

+

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/handling-text-input.html b/releases/0.37/docs/handling-text-input.html new file mode 100644 index 00000000000..aae9f451909 --- /dev/null +++ b/releases/0.37/docs/handling-text-input.html @@ -0,0 +1,46 @@ +Handling Text Input

Handling Text Input #

TextInput is a basic component that allows the user to enter text. It has an onChangeText prop that takes +a function to be called every time the text changed, and an onSubmitEditing prop that takes a function to be called when the text is submitted.

For example, let's say that as the user types, you're translating their words into a different language. In this new language, every single word is written the same way: 🍕. So the sentence "Hello there Bob" would be translated +as "🍕🍕🍕".

import React, { Component } from 'react'; +import { AppRegistry, Text, TextInput, View } from 'react-native'; + +class PizzaTranslator extends Component { + constructor(props) { + super(props); + this.state = {text: ''}; + } + + render() { + return ( + <View style={{padding: 10}}> + <TextInput + style={{height: 40}} + placeholder="Type here to translate!" + onChangeText={(text) => this.setState({text})} + /> + <Text style={{padding: 10, fontSize: 42}}> + {this.state.text.split(' ').map((word) => word && '🍕').join(' ')} + </Text> + </View> + ); + } +} + +AppRegistry.registerComponent('PizzaTranslator', () => PizzaTranslator);

In this example, we store text in the state, because it changes over time.

There are a lot more things you might want to do with a text input. For example, you could validate the text inside while the user types. For more detailed examples, see the React docs on controlled components, or the reference docs for TextInput.

Text input is probably the simplest example of a component whose state naturally changes over time. Next, let's look at another type of component like this is one that controls layout, and learn about the ScrollView.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/handling-touches.html b/releases/0.37/docs/handling-touches.html new file mode 100644 index 00000000000..bd0a43cd1f1 --- /dev/null +++ b/releases/0.37/docs/handling-touches.html @@ -0,0 +1,31 @@ +Handling Touches

Handling Touches #

Users interact with mobile apps mainly through touch. They can use a combination of gestures, such as tapping on a button, scrolling a list, or zooming on a map.

React Native provides components to handle common gestures, such as taps and swipes, as well as a comprehensive gesture responder system to allow for more advanced gesture recognition.

Tappable Components #

You can use "Touchable" components when you want to capture a tapping gesture. They take a function through the onPress props which will be called when the touch begins and ends within the bounds of the component.

Example:

class MyButton extends Component { + _onPressButton() { + console.log("You tapped the button!"); + } + + render() { + return ( + <TouchableHighlight onPress={this._onPressButton}> + <Text>Button</Text> + </TouchableHighlight> + ); + } +}

Tappable components should provide feedback that show the user what is handling their touch, and what will happen when they lift their finger. The user should also be able to cancel a tap by dragging their finger away.

Which component you use will depend on what kind of feedback you want to provide:

  • Generally, you can use TouchableHighlight anywhere you would use a button or link on web. The view's background will be darkened when the user presses down on the button.

  • You may consider using TouchableNativeFeedback on Android to display ink surface reaction ripples that respond to the user's touch.

  • TouchableOpacity can be used to provide feedback by reducing the opacity of the button, allowing the background to be seen through while the user is pressing down.

  • If you need to handle a tap gesture but you don't want any feedback to be displayed, use TouchableWithoutFeedback.

Long presses #

In some cases, you may want to detect when a user presses and holds a view for a set amount of time. These long presses can be handled by passing a function to the onLongPress props of any of the touchable components listed above.

Scrolling lists and swiping views #

A common pattern to many mobile apps is the scrollable list of items. Users interact with these using panning or swiping gestures. The ScrollView component displays a list of items that can be scrolled using these gestures.

ScrollViews can scroll vertically or horizontally, and can be configured to allow paging through views using swiping gestures by using the pagingEnabled props. Swiping horizontally between views can also be implemented on Android using the ViewPagerAndroid component.

A ListView is a special kind of ScrollView that is best suited for displaying long vertical lists of items. It can also display section headers and footers, similar to UITableViews on iOS.

Pinch-to-zoom #

A ScrollView with a single item can be used to allow the user to zoom content. Set up the maximumZoomScale and minimumZoomScale props and your user will be able to use pinch and expand gestures to zoom in and out.

Handling additional gestures #

If you want to allow a user to drag a view around the screen, or you want to implement your own custom pan/drag gesture, take a look at the PanResponder API or the gesture responder system docs.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/headless-js-android.html b/releases/0.37/docs/headless-js-android.html new file mode 100644 index 00000000000..16eb706bda6 --- /dev/null +++ b/releases/0.37/docs/headless-js-android.html @@ -0,0 +1,34 @@ +Headless JS

Headless JS #

Headless JS is a way to run tasks in JavaScript while your app is in the background. It can be used, for example, to sync fresh data, handle push notifications, or play music.

The JS API #

A task is a simple async function that you register on AppRegistry, similar to registering React applications:

AppRegistry.registerHeadlessTask('SomeTaskName', () => require('SomeTaskName'));

Then, in SomeTaskName.js:

module.exports = async (taskData) => { + // do stuff +}

You can do anything in your task as long as it doesn't touch UI: network requests, timers and so on. Once your task completes (i.e. the promise is resolved), React Native will go into "paused" mode (unless there are other tasks running, or there is a foreground app).

The Java API #

Yes, this does still require some native code, but it's pretty thin. You need to extend HeadlessJsTaskService and override getTaskConfig, e.g.:

public class MyTaskService extends FbHeadlessJsTaskService { + + @Override + protected @Nullable HeadlessJsTaskConfig getTaskConfig(Intent intent) { + Bundle extras = intent.getExtras(); + if (extras != null) { + return new HeadlessJsTaskConfig( + "SomeTaskName", + Arguments.fromBundle(extras), + 5000); + } + return null; + } +}

Now, whenever you start your service, e.g. as a periodic task or in response to some system event / broadcast, JS will spin up, run your task, then spin down.

Caveats #

  • By default, your app will crash if you try to run a task while the app is in the foreground. This is to prevent developers from shooting themselves in the foot by doing a lot of work in a task and slowing the UI. There is a way around this.
  • If you start your service from a BroadcastReceiver, make sure to call HeadlessJsTaskService.acquireWakelockNow() before returning from onReceive().

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/height-and-width.html b/releases/0.37/docs/height-and-width.html new file mode 100644 index 00000000000..0ac9830b779 --- /dev/null +++ b/releases/0.37/docs/height-and-width.html @@ -0,0 +1,52 @@ +Height and Width

Height and Width #

A component's height and width determine its size on the screen.

Fixed Dimensions #

The simplest way to set the dimensions of a component is by adding a fixed width and height to style. All dimensions in React Native are unitless, and represent density-independent pixels.

import React, { Component } from 'react'; +import { AppRegistry, View } from 'react-native'; + +class FixedDimensionsBasics extends Component { + render() { + return ( + <View> + <View style={{width: 50, height: 50, backgroundColor: 'powderblue'}} /> + <View style={{width: 100, height: 100, backgroundColor: 'skyblue'}} /> + <View style={{width: 150, height: 150, backgroundColor: 'steelblue'}} /> + </View> + ); + } +}; + +AppRegistry.registerComponent('AwesomeProject', () => FixedDimensionsBasics);

Setting dimensions this way is common for components that should always render at exactly the same size, regardless of screen dimensions.

Flex Dimensions #

Use flex in a component's style to have the component expand and shrink dynamically based on available space. Normally you will use flex: 1, which tells a component to fill all available space, shared evenly amongst each other component with the same parent. The larger the flex given, the higher the ratio of space a component will take compared to its siblings.

A component can only expand to fill available space if its parent has dimensions greater than 0. If a parent does not have either a fixed width and height or flex, the parent will have dimensions of 0 and the flex children will not be visible.

import React, { Component } from 'react'; +import { AppRegistry, View } from 'react-native'; + +class FlexDimensionsBasics extends Component { + render() { + return ( + // Try removing the `flex: 1` on the parent View. + // The parent will not have dimensions, so the children can't expand. + // What if you add `height: 300` instead of `flex: 1`? + <View style={{flex: 1}}> + <View style={{flex: 1, backgroundColor: 'powderblue'}} /> + <View style={{flex: 2, backgroundColor: 'skyblue'}} /> + <View style={{flex: 3, backgroundColor: 'steelblue'}} /> + </View> + ); + } +}; + +AppRegistry.registerComponent('AwesomeProject', () => FlexDimensionsBasics);

After you can control a component's size, the next step is to learn how to lay it out on the screen.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/image.html b/releases/0.37/docs/image.html new file mode 100644 index 00000000000..3621168abaf --- /dev/null +++ b/releases/0.37/docs/image.html @@ -0,0 +1,820 @@ +Image

Image #

A React component for displaying different types of images, +including network images, static resources, temporary local images, and +images from local disk, such as the camera roll.

This example shows both fetching and displaying an image from local storage as well as on from +network.

import React, { Component } from 'react'; +import { AppRegistry, View, Image } from 'react-native'; + +class DisplayAnImage extends Component { + render() { + return ( + <View> + <Image + source={require('./img/favicon.png')} + /> + <Image + style={{width: 50, height: 50}} + source={{uri: 'https://facebook.github.io/react/img/logo_og.png'}} + /> + </View> + ); + } +} + +// App registration and rendering +AppRegistry.registerComponent('DisplayAnImage', () => DisplayAnImage);

You can also add style to an image:

import React, { Component } from 'react'; +import { AppRegistry, View, Image, StyleSheet} from 'react-native'; + +const styles = StyleSheet.create({ + stretch: { + width: 50, + height: 200 + } +}); + +class DisplayAnImageWithStyle extends Component { + render() { + return ( + <View> + <Image + style={styles.stretch} + source={require('./img/favicon.png')} + /> + </View> + ); + } +} + +// App registration and rendering +AppRegistry.registerComponent( + 'DisplayAnImageWithStyle', + () => DisplayAnImageWithStyle +);

GIF and WebP support on Android #

By default, GIF and WebP are not supported on Android.

You will need to add some optional modules in android/app/build.gradle, depending on the needs of your app.

dependencies { + // If your app supports Android versions before Ice Cream Sandwich (API level 14) + compile 'com.facebook.fresco:animated-base-support:0.11.0' + + // For animated GIF support + compile 'com.facebook.fresco:animated-gif:0.11.0' + + // For WebP support, including animated WebP + compile 'com.facebook.fresco:animated-webp:0.11.0' + compile 'com.facebook.fresco:webpsupport:0.11.0' + + // For WebP support, without animations + compile 'com.facebook.fresco:webpsupport:0.11.0' +}

Also, if you use GIF with ProGuard, you will need to add this rule in proguard-rules.pro :

-keep class com.facebook.imagepipeline.animated.factory.AnimatedFactoryImpl { + public AnimatedFactoryImpl(com.facebook.imagepipeline.bitmaps.PlatformBitmapFactory, com.facebook.imagepipeline.core.ExecutorSupplier); +}

Props #

onLayout function #

Invoked on mount and layout changes with +{nativeEvent: {layout: {x, y, width, height}}}.

onLoad function #

Invoked when load completes successfully.

onLoadEnd function #

Invoked when load either succeeds or fails.

onLoadStart function #

Invoked on load start.

e.g., onLoadStart={(e) => this.setState({loading: true})}

resizeMode enum('cover', 'contain', 'stretch', 'repeat', 'center') #

Determines how to resize the image when the frame doesn't match the raw +image dimensions.

  • cover: Scale the image uniformly (maintain the image's aspect ratio) +so that both dimensions (width and height) of the image will be equal +to or larger than the corresponding dimension of the view (minus padding).

  • contain: Scale the image uniformly (maintain the image's aspect ratio) +so that both dimensions (width and height) of the image will be equal to +or less than the corresponding dimension of the view (minus padding).

  • stretch: Scale width and height independently, This may change the +aspect ratio of the src.

  • repeat: Repeat the image to cover the frame of the view. The +image will keep it's size and aspect ratio. (iOS only)

source ImageSourcePropType #

The image source (either a remote URL or a local file resource).

This prop can also contain several remote URLs, specified together with +their width and height and potentially with scale/other URI arguments. +The native side will then choose the best uri to display based on the +measured size of the image container.

style style #

backfaceVisibility enum('visible', 'hidden')
backgroundColor color
borderBottomLeftRadius number
borderBottomRightRadius number
borderColor color
borderRadius number
borderTopLeftRadius number
borderTopRightRadius number
borderWidth number
opacity number
overflow enum('visible', 'hidden')
resizeMode Object.keys(ImageResizeMode)
tintColor color

Changes the color of all the non-transparent pixels to the tintColor.

androidoverlayColor string

When the image has rounded corners, specifying an overlayColor will +cause the remaining space in the corners to be filled with a solid color. +This is useful in cases which are not supported by the Android +implementation of rounded corners: + - Certain resize modes, such as 'contain' + - Animated GIFs

A typical way to use this prop is with images displayed on a solid +background and setting the overlayColor to the same color +as the background.

For details of how this works under the hood, see +http://frescolib.org/docs/rounded-corners-and-circles.html

ImageResizeMode is an Enum for different image resizing modes, set via the +resizeMode style property on Image components. The values are contain, cover, +stretch, center, repeat.

testID string #

A unique identifier for this element to be used in UI Automation +testing scripts.

androidresizeMethod enum('auto', 'resize', 'scale') #

The mechanism that should be used to resize the image when the image's dimensions +differ from the image view's dimensions. Defaults to auto.

  • auto: Use heuristics to pick between resize and scale.

  • resize: A software operation which changes the encoded image in memory before it +gets decoded. This should be used instead of scale when the image is much larger +than the view.

  • scale: The image gets drawn downscaled or upscaled. Compared to resize, scale is +faster (usually hardware accelerated) and produces higher quality images. This +should be used if the image is smaller than the view. It should also be used if the +image is slightly bigger than the view.

More details about resize and scale can be found at http://frescolib.org/docs/resizing-rotating.html.

iosaccessibilityLabel string #

The text that's read by the screen reader when the user interacts with +the image.

iosaccessible bool #

When true, indicates the image is an accessibility element.

iosblurRadius number #

blurRadius: the blur radius of the blur filter added to the image

ioscapInsets {top: number, left: number, bottom: number, right: number} #

When the image is resized, the corners of the size specified +by capInsets will stay a fixed size, but the center content and borders +of the image will be stretched. This is useful for creating resizable +rounded buttons, shadows, and other resizable assets. More info in the +official Apple documentation.

iosdefaultSource {uri: string, width: number, height: number, scale: number}, number #

A static image to display while loading the image source.

  • uri - a string representing the resource identifier for the image, which +should be either a local file path or the name of a static image resource +(which should be wrapped in the require('./path/to/image.png') function).
  • width, height - can be specified if known at build time, in which case +these will be used to set the default <Image/> component dimensions.
  • scale - used to indicate the scale factor of the image. Defaults to 1.0 if +unspecified, meaning that one image pixel equates to one display point / DIP.
  • number - Opaque type returned by something like require('./image.jpg').

iosonError function #

Invoked on load error with {nativeEvent: {error}}.

iosonPartialLoad function #

Invoked when a partial load of the image is complete. The definition of +what constitutes a "partial load" is loader specific though this is meant +for progressive JPEG loads.

iosonProgress function #

Invoked on download progress with {nativeEvent: {loaded, total}}.

Methods #

static getSize(uri, success, failure): #

Retrieve the width and height (in pixels) of an image prior to displaying it. +This method can fail if the image cannot be found, or fails to download.

In order to retrieve the image dimensions, the image may first need to be +loaded or downloaded, after which it will be cached. This means that in +principle you could use this method to preload images, however it is not +optimized for that purpose, and may in future be implemented in a way that +does not fully load/download the image data. A proper, supported way to +preload images will be provided as a separate API.

Parameters:
Name and TypeDescription
uri

string

The location of the image.

success

function

The function that will be called if the image was sucessfully found and width +and height retrieved.

failure

function

The function that will be called if there was an error, such as failing to +to retrieve the image.

static prefetch(url): #

Prefetches a remote image for later use by downloading it to the disk +cache

Parameters:
Name and TypeDescription
url

string

The remote location of the image.

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + ActivityIndicator, + Image, + Platform, + StyleSheet, + Text, + View, +} = ReactNative; + +var base64Icon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAABLCAQAAACSR7JhAAADtUlEQVR4Ac3YA2Bj6QLH0XPT1Fzbtm29tW3btm3bfLZtv7e2ObZnms7d8Uw098tuetPzrxv8wiISrtVudrG2JXQZ4VOv+qUfmqCGGl1mqLhoA52oZlb0mrjsnhKpgeUNEs91Z0pd1kvihA3ULGVHiQO2narKSHKkEMulm9VgUyE60s1aWoMQUbpZOWE+kaqs4eLEjdIlZTcFZB0ndc1+lhB1lZrIuk5P2aib1NBpZaL+JaOGIt0ls47SKzLC7CqrlGF6RZ09HGoNy1lYl2aRSWL5GuzqWU1KafRdoRp0iOQEiDzgZPnG6DbldcomadViflnl/cL93tOoVbsOLVM2jylvdWjXolWX1hmfZbGR/wjypDjFLSZIRov09BgYmtUqPQPlQrPapecLgTIy0jMgPKtTeob2zWtrGH3xvjUkPCtNg/tm1rjwrMa+mdUkPd3hWbH0jArPGiU9ufCsNNWFZ40wpwn+62/66R2RUtoso1OB34tnLOcy7YB1fUdc9e0q3yru8PGM773vXsuZ5YIZX+5xmHwHGVvlrGPN6ZSiP1smOsMMde40wKv2VmwPPVXNut4sVpUreZiLBHi0qln/VQeI/LTMYXpsJtFiclUN+5HVZazim+Ky+7sAvxWnvjXrJFneVtLWLyPJu9K3cXLWeOlbMTlrIelbMDlrLenrjEQOtIF+fuI9xRp9ZBFp6+b6WT8RrxEpdK64BuvHgDk+vUy+b5hYk6zfyfs051gRoNO1usU12WWRWL73/MMEy9pMi9qIrR4ZpV16Rrvduxazmy1FSvuFXRkqTnE7m2kdb5U8xGjLw/spRr1uTov4uOgQE+0N/DvFrG/Jt7i/FzwxbA9kDanhf2w+t4V97G8lrT7wc08aA2QNUkuTfW/KimT01wdlfK4yEw030VfT0RtZbzjeMprNq8m8tnSTASrTLti64oBNdpmMQm0eEwvfPwRbUBywG5TzjPCsdwk3IeAXjQblLCoXnDVeoAz6SfJNk5TTzytCNZk/POtTSV40NwOFWzw86wNJRpubpXsn60NJFlHeqlYRbslqZm2jnEZ3qcSKgm0kTli3zZVS7y/iivZTweYXJ26Y+RTbV1zh3hYkgyFGSTKPfRVbRqWWVReaxYeSLarYv1Qqsmh1s95S7G+eEWK0f3jYKTbV6bOwepjfhtafsvUsqrQvrGC8YhmnO9cSCk3yuY984F1vesdHYhWJ5FvASlacshUsajFt2mUM9pqzvKGcyNJW0arTKN1GGGzQlH0tXwLDgQTurS8eIQAAAABJRU5ErkJggg=='; + +var ImageCapInsetsExample = require('./ImageCapInsetsExample'); +const IMAGE_PREFETCH_URL = 'http://facebook.github.io/origami/public/images/blog-hero.jpg?r=1&t=' + Date.now(); +var prefetchTask = Image.prefetch(IMAGE_PREFETCH_URL); + +var NetworkImageCallbackExample = React.createClass({ + getInitialState: function() { + return { + events: [], + startLoadPrefetched: false, + mountTime: new Date(), + }; + }, + + componentWillMount() { + this.setState({mountTime: new Date()}); + }, + + render: function() { + var { mountTime } = this.state; + + return ( + <View> + <Image + source={this.props.source} + style={[styles.base, {overflow: 'visible'}]} + onLoadStart={() => this._loadEventFired(`✔ onLoadStart (+${new Date() - mountTime}ms)`)} + onLoad={(event) => { + // Currently this image source feature is only available on iOS. + if (event.nativeEvent.source) { + const url = event.nativeEvent.source.url; + this._loadEventFired(`✔ onLoad (+${new Date() - mountTime}ms) for URL ${url}`); + } else { + this._loadEventFired(`✔ onLoad (+${new Date() - mountTime}ms)`); + } + }} + onLoadEnd={() => { + this._loadEventFired(`✔ onLoadEnd (+${new Date() - mountTime}ms)`); + this.setState({startLoadPrefetched: true}, () => { + prefetchTask.then(() => { + this._loadEventFired(`✔ Prefetch OK (+${new Date() - mountTime}ms)`); + }, error => { + this._loadEventFired(`✘ Prefetch failed (+${new Date() - mountTime}ms)`); + }); + }); + }} + /> + {this.state.startLoadPrefetched ? + <Image + source={this.props.prefetchedSource} + style={[styles.base, {overflow: 'visible'}]} + onLoadStart={() => this._loadEventFired(`✔ (prefetched) onLoadStart (+${new Date() - mountTime}ms)`)} + onLoad={(event) => { + // Currently this image source feature is only available on iOS. + if (event.nativeEvent.source) { + const url = event.nativeEvent.source.url; + this._loadEventFired(`✔ (prefetched) onLoad (+${new Date() - mountTime}ms) for URL ${url}`); + } else { + this._loadEventFired(`✔ (prefetched) onLoad (+${new Date() - mountTime}ms)`); + } + }} + onLoadEnd={() => this._loadEventFired(`✔ (prefetched) onLoadEnd (+${new Date() - mountTime}ms)`)} + /> + : null} + <Text style={{marginTop: 20}}> + {this.state.events.join('\n')} + </Text> + </View> + ); + }, + + _loadEventFired(event) { + this.setState((state) => { + return state.events = [...state.events, event]; + }); + } +}); + +var NetworkImageExample = React.createClass({ + getInitialState: function() { + return { + error: false, + loading: false, + progress: 0 + }; + }, + render: function() { + var loader = this.state.loading ? + <View style={styles.progress}> + <Text>{this.state.progress}%</Text> + <ActivityIndicator style={{marginLeft:5}} /> + </View> : null; + return this.state.error ? + <Text>{this.state.error}</Text> : + <Image + source={this.props.source} + style={[styles.base, {overflow: 'visible'}]} + onLoadStart={(e) => this.setState({loading: true})} + onError={(e) => this.setState({error: e.nativeEvent.error, loading: false})} + onProgress={(e) => this.setState({progress: Math.round(100 * e.nativeEvent.loaded / e.nativeEvent.total)})} + onLoad={() => this.setState({loading: false, error: false})}> + {loader} + </Image>; + } +}); + +var ImageSizeExample = React.createClass({ + getInitialState: function() { + return { + width: 0, + height: 0, + }; + }, + componentDidMount: function() { + Image.getSize(this.props.source.uri, (width, height) => { + this.setState({width, height}); + }); + }, + render: function() { + return ( + <View style={{flexDirection: 'row'}}> + <Image + style={{ + width: 60, + height: 60, + backgroundColor: 'transparent', + marginRight: 10, + }} + source={this.props.source} /> + <Text> + Actual dimensions:{'\n'} + Width: {this.state.width}, Height: {this.state.height} + </Text> + </View> + ); + }, +}); + +var MultipleSourcesExample = React.createClass({ + getInitialState: function() { + return { + width: 30, + height: 30, + }; + }, + render: function() { + return ( + <View> + <View style={{flexDirection: 'row', justifyContent: 'space-between'}}> + <Text + style={styles.touchableText} + onPress={this.decreaseImageSize} > + Decrease image size + </Text> + <Text + style={styles.touchableText} + onPress={this.increaseImageSize} > + Increase image size + </Text> + </View> + <Text>Container image size: {this.state.width}x{this.state.height} </Text> + <View + style={{height: this.state.height, width: this.state.width}} > + <Image + style={{flex: 1}} + source={[ + {uri: 'http://facebook.github.io/react/img/logo_small.png', width: 38, height: 38}, + {uri: 'http://facebook.github.io/react/img/logo_small_2x.png', width: 76, height: 76}, + {uri: 'http://facebook.github.io/react/img/logo_og.png', width: 400, height: 400} + ]} + /> + </View> + </View> + ); + }, + increaseImageSize: function() { + if (this.state.width >= 100) { + return; + } + this.setState({ + width: this.state.width + 10, + height: this.state.height + 10, + }); + }, + decreaseImageSize: function() { + if (this.state.width <= 10) { + return; + } + this.setState({ + width: this.state.width - 10, + height: this.state.height - 10, + }); + }, +}); + +exports.displayName = (undefined: ?string); +exports.framework = 'React'; +exports.title = '<Image>'; +exports.description = 'Base component for displaying different types of images.'; + +exports.examples = [ + { + title: 'Plain Network Image', + description: 'If the `source` prop `uri` property is prefixed with ' + + '"http", then it will be downloaded from the network.', + render: function() { + return ( + <Image + source={{uri: 'http://facebook.github.io/react/img/logo_og.png'}} + style={styles.base} + /> + ); + }, + }, + { + title: 'Plain Static Image', + description: 'Static assets should be placed in the source code tree, and ' + + 'required in the same way as JavaScript modules.', + render: function() { + return ( + <View style={styles.horizontal}> + <Image source={require('./uie_thumb_normal.png')} style={styles.icon} /> + <Image source={require('./uie_thumb_selected.png')} style={styles.icon} /> + <Image source={require('./uie_comment_normal.png')} style={styles.icon} /> + <Image source={require('./uie_comment_highlighted.png')} style={styles.icon} /> + </View> + ); + }, + }, + { + title: 'Image Loading Events', + render: function() { + return ( + <NetworkImageCallbackExample source={{uri: 'http://facebook.github.io/origami/public/images/blog-hero.jpg?r=1&t=' + Date.now()}} + prefetchedSource={{uri: IMAGE_PREFETCH_URL}}/> + ); + }, + }, + { + title: 'Error Handler', + render: function() { + return ( + <NetworkImageExample source={{uri: 'http://TYPO_ERROR_facebook.github.io/react/img/logo_og.png'}} /> + ); + }, + platform: 'ios', + }, + { + title: 'Image Download Progress', + render: function() { + return ( + <NetworkImageExample source={{uri: 'http://facebook.github.io/origami/public/images/blog-hero.jpg?r=1'}}/> + ); + }, + platform: 'ios', + }, + { + title: 'defaultSource', + description: 'Show a placeholder image when a network image is loading', + render: function() { + return ( + <Image + defaultSource={require('./bunny.png')} + source={{uri: 'http://facebook.github.io/origami/public/images/birds.jpg'}} + style={styles.base} + /> + ); + }, + platform: 'ios', + }, + { + title: 'Border Color', + render: function() { + return ( + <View style={styles.horizontal}> + <Image + source={smallImage} + style={[ + styles.base, + styles.background, + {borderWidth: 3, borderColor: '#f099f0'} + ]} + /> + </View> + ); + }, + }, + { + title: 'Border Width', + render: function() { + return ( + <View style={styles.horizontal}> + <Image + source={smallImage} + style={[ + styles.base, + styles.background, + {borderWidth: 5, borderColor: '#f099f0'} + ]} + /> + </View> + ); + }, + }, + { + title: 'Border Radius', + render: function() { + return ( + <View style={styles.horizontal}> + <Image + style={[styles.base, {borderRadius: 5}]} + source={fullImage} + /> + <Image + style={[styles.base, styles.leftMargin, {borderRadius: 19}]} + source={fullImage} + /> + </View> + ); + }, + }, + { + title: 'Background Color', + render: function() { + return ( + <View style={styles.horizontal}> + <Image source={smallImage} style={styles.base} /> + <Image + style={[ + styles.base, + styles.leftMargin, + {backgroundColor: 'rgba(0, 0, 100, 0.25)'} + ]} + source={smallImage} + /> + <Image + style={[styles.base, styles.leftMargin, {backgroundColor: 'red'}]} + source={smallImage} + /> + <Image + style={[styles.base, styles.leftMargin, {backgroundColor: 'black'}]} + source={smallImage} + /> + </View> + ); + }, + }, + { + title: 'Opacity', + render: function() { + return ( + <View style={styles.horizontal}> + <Image + style={[styles.base, {opacity: 1}]} + source={fullImage} + /> + <Image + style={[styles.base, styles.leftMargin, {opacity: 0.8}]} + source={fullImage} + /> + <Image + style={[styles.base, styles.leftMargin, {opacity: 0.6}]} + source={fullImage} + /> + <Image + style={[styles.base, styles.leftMargin, {opacity: 0.4}]} + source={fullImage} + /> + <Image + style={[styles.base, styles.leftMargin, {opacity: 0.2}]} + source={fullImage} + /> + <Image + style={[styles.base, styles.leftMargin, {opacity: 0}]} + source={fullImage} + /> + </View> + ); + }, + }, + { + title: 'Nesting', + render: function() { + return ( + <Image + style={{width: 60, height: 60, backgroundColor: 'transparent'}} + source={fullImage}> + <Text style={styles.nestedText}> + React + </Text> + </Image> + ); + }, + }, + { + title: 'Tint Color', + description: 'The `tintColor` style prop changes all the non-alpha ' + + 'pixels to the tint color.', + render: function() { + return ( + <View> + <View style={styles.horizontal}> + <Image + source={require('./uie_thumb_normal.png')} + style={[styles.icon, {borderRadius: 5, tintColor: '#5ac8fa' }]} + /> + <Image + source={require('./uie_thumb_normal.png')} + style={[styles.icon, styles.leftMargin, {borderRadius: 5, tintColor: '#4cd964' }]} + /> + <Image + source={require('./uie_thumb_normal.png')} + style={[styles.icon, styles.leftMargin, {borderRadius: 5, tintColor: '#ff2d55' }]} + /> + <Image + source={require('./uie_thumb_normal.png')} + style={[styles.icon, styles.leftMargin, {borderRadius: 5, tintColor: '#8e8e93' }]} + /> + </View> + <Text style={styles.sectionText}> + It also works with downloaded images: + </Text> + <View style={styles.horizontal}> + <Image + source={smallImage} + style={[styles.base, {borderRadius: 5, tintColor: '#5ac8fa' }]} + /> + <Image + source={smallImage} + style={[styles.base, styles.leftMargin, {borderRadius: 5, tintColor: '#4cd964' }]} + /> + <Image + source={smallImage} + style={[styles.base, styles.leftMargin, {borderRadius: 5, tintColor: '#ff2d55' }]} + /> + <Image + source={smallImage} + style={[styles.base, styles.leftMargin, {borderRadius: 5, tintColor: '#8e8e93' }]} + /> + </View> + </View> + ); + }, + }, + { + title: 'Resize Mode', + description: 'The `resizeMode` style prop controls how the image is ' + + 'rendered within the frame.', + render: function() { + return ( + <View> + {[smallImage, fullImage].map((image, index) => { + return ( + <View key={index}> + <View style={styles.horizontal}> + <View> + <Text style={[styles.resizeModeText]}> + Contain + </Text> + <Image + style={styles.resizeMode} + resizeMode={Image.resizeMode.contain} + source={image} + /> + </View> + <View style={styles.leftMargin}> + <Text style={[styles.resizeModeText]}> + Cover + </Text> + <Image + style={styles.resizeMode} + resizeMode={Image.resizeMode.cover} + source={image} + /> + </View> + </View> + <View style={styles.horizontal}> + <View> + <Text style={[styles.resizeModeText]}> + Stretch + </Text> + <Image + style={styles.resizeMode} + resizeMode={Image.resizeMode.stretch} + source={image} + /> + </View> + { Platform.OS === 'ios' ? + <View style={styles.leftMargin}> + <Text style={[styles.resizeModeText]}> + Repeat + </Text> + <Image + style={styles.resizeMode} + resizeMode={Image.resizeMode.repeat} + source={image} + /> + </View> + : null } + { Platform.OS === 'android' ? + <View style={styles.leftMargin}> + <Text style={[styles.resizeModeText]}> + Center + </Text> + <Image + style={styles.resizeMode} + resizeMode={Image.resizeMode.center} + source={image} + /> + </View> + : null } + </View> + </View> + ); + })} + </View> + ); + }, + }, + { + title: 'Animated GIF', + render: function() { + return ( + <Image + style={styles.gif} + source={{uri: 'http://38.media.tumblr.com/9e9bd08c6e2d10561dd1fb4197df4c4e/tumblr_mfqekpMktw1rn90umo1_500.gif'}} + /> + ); + }, + platform: 'ios', + }, + { + title: 'Base64 image', + render: function() { + return ( + <Image + style={styles.base64} + source={{uri: base64Icon, scale: 3}} + /> + ); + }, + platform: 'ios', + }, + { + title: 'Cap Insets', + description: + 'When the image is resized, the corners of the size specified ' + + 'by capInsets will stay a fixed size, but the center content and ' + + 'borders of the image will be stretched. This is useful for creating ' + + 'resizable rounded buttons, shadows, and other resizable assets.', + render: function() { + return <ImageCapInsetsExample />; + }, + platform: 'ios', + }, + { + title: 'Image Size', + render: function() { + return <ImageSizeExample source={fullImage} />; + }, + }, + { + title: 'MultipleSourcesExample', + description: + 'The `source` prop allows passing in an array of uris, so that native to choose which image ' + + 'to diplay based on the size of the of the target image', + render: function() { + return <MultipleSourcesExample />; + }, + }, + { + title: 'Legacy local image', + description: + 'Images shipped with the native bundle, but not managed ' + + 'by the JS packager', + render: function() { + return ( + <Image + source={require('image!legacy_image')} + /> + ); + }, + }, + { + title: 'Bundled images', + description: + 'Images shipped in a separate native bundle', + render: function() { + return ( + <View style={{flexDirection: 'row'}}> + <Image + source={{ + url: 'ImageInBundle', + bundle: 'UIExplorerBundle', + width: 100, + height: 100, + }} + style={{borderColor: 'yellow', borderWidth: 4}} + /> + <Image + source={{ + url: 'ImageInAssetCatalog', + bundle: 'UIExplorerBundle', + width: 100, + height: 100, + }} + style={{marginLeft: 10, borderColor: 'blue', borderWidth: 4}} + /> + </View> + ); + }, + platform: 'ios', + }, +]; + +var fullImage = {uri: 'http://facebook.github.io/react/img/logo_og.png'}; +var smallImage = {uri: 'http://facebook.github.io/react/img/logo_small_2x.png'}; + +var styles = StyleSheet.create({ + base: { + width: 38, + height: 38, + }, + progress: { + flex: 1, + alignItems: 'center', + flexDirection: 'row', + width: 100 + }, + leftMargin: { + marginLeft: 10, + }, + background: { + backgroundColor: '#222222' + }, + sectionText: { + marginVertical: 6, + }, + nestedText: { + marginLeft: 12, + marginTop: 20, + backgroundColor: 'transparent', + color: 'white' + }, + resizeMode: { + width: 90, + height: 60, + borderWidth: 0.5, + borderColor: 'black' + }, + resizeModeText: { + fontSize: 11, + marginBottom: 3, + }, + icon: { + width: 15, + height: 15, + }, + horizontal: { + flexDirection: 'row', + }, + gif: { + flex: 1, + height: 200, + }, + base64: { + flex: 1, + height: 50, + resizeMode: 'contain', + }, + touchableText: { + fontWeight: '500', + color: 'blue', + }, +});
\ No newline at end of file diff --git a/releases/0.37/docs/imageeditor.html b/releases/0.37/docs/imageeditor.html new file mode 100644 index 00000000000..cfec2d8439c --- /dev/null +++ b/releases/0.37/docs/imageeditor.html @@ -0,0 +1,24 @@ +ImageEditor

ImageEditor #

Methods #

static cropImage(uri, cropData, success, failure) #

Crop the image specified by the URI param. If URI points to a remote +image, it will be downloaded automatically. If the image cannot be +loaded/downloaded, the failure callback will be called.

If the cropping process is successful, the resultant cropped image +will be stored in the ImageStore, and the URI returned in the success +callback will point to the image in the store. Remember to delete the +cropped image from the ImageStore when you are done with it.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/imagepickerios.html b/releases/0.37/docs/imagepickerios.html new file mode 100644 index 00000000000..280a005ed5c --- /dev/null +++ b/releases/0.37/docs/imagepickerios.html @@ -0,0 +1,19 @@ +ImagePickerIOS

ImagePickerIOS #

Methods #

static canRecordVideos(callback) #

static canUseCamera(callback) #

static openCameraDialog(config, successCallback, cancelCallback) #

static openSelectDialog(config, successCallback, cancelCallback) #

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/images.html b/releases/0.37/docs/images.html new file mode 100644 index 00000000000..ce4be5875a8 --- /dev/null +++ b/releases/0.37/docs/images.html @@ -0,0 +1,42 @@ +Images

Images #

Static Image Resources #

As of 0.14 release, React Native provides a unified way of managing images in your iOS and Android apps. To add a static image to your app, place it somewhere in your source code tree and reference it like this:

<Image source={require('./my-icon.png')} />

The image name is resolved the same way JS modules are resolved. In the example above the packager will look for my-icon.png in the same folder as the component that requires it. Also if you have my-icon.ios.png and my-icon.android.png, the packager will pick the file depending on the platform you are running on.

You can also use @2x, @3x, etc. suffix in the file name to provide images for different screen densities. For example, if you have the following file structure:

. +├── button.js +└── img + ├── check@2x.png + └── check@3x.png

And button.js code contains

<Image source={require('./img/check.png')} />

Packager will bundle and serve the image corresponding to device's screen density, e.g. on iPhone 5s check@2x.png will be used, on Nexus 5 – check@3x.png. If there is no image matching the screen density, the closest best option will be selected.

On Windows, you might need to restart the packager if you add new images to your project.

Here are some benefits that you get:

  1. Same system on iOS and Android.
  2. Images live in the same folder as your JS code. Components are self-contained.
  3. No global namespace, i.e. you don't have worry about name collisions.
  4. Only the images that are actually used will be packaged into your app.
  5. Adding and changing images doesn't require app recompilation, just refresh the simulator as you normally do.
  6. The packager knows the image dimensions, no need to duplicate it in the code.
  7. Images can be distributed via npm packages.

Note that in order for this to work, the image name in require has to be known statically.

// GOOD +<Image source={require('./my-icon.png')} /> + +// BAD +var icon = this.props.active ? 'my-icon-active' : 'my-icon-inactive'; +<Image source={require('./' + icon + '.png')} /> + +// GOOD +var icon = this.props.active ? require('./my-icon-active.png') : require('./my-icon-inactive.png'); +<Image source={icon} />

Note that image sources required this way include size (width, height) info for the Image. If you need to scale the image dynamically (i.e. via flex), you may need to manually set { width: undefined, height: undefined } on the style attribute.

Available React Native 0.14+. If you've generated your project with 0.13 or earlier, read this. The new asset system relies on build hooks for Xcode and Gradle that are included in new projects generated with react-native init. If you generated your projects before that, you'll have to manually add them to your projects to use the new images asset system. See Upgrading for instructions on how to do this.

Images From Hybrid App's Resources #

If you are building a hybrid app (some UIs in React Native, some UIs in platform code) you can still use images that are already bundled into the app (via Xcode asset catalogs or Android drawable folder):

<Image source={{uri: 'app_icon'}} style={{width: 40, height: 40}} />

Note that this approach provides no safety checks. It's up to you to guarantee that those images are available in the application. Also you have to specify image dimensions manually.

Network Images #

Many of the images you will display in your app will not be available at compile time, or you will want to load some dynamically to keep the binary size down. Unlike with static resources, you will need to manually specify the dimensions of your image.

// GOOD +<Image source={{uri: 'https://facebook.github.io/react/img/logo_og.png'}} + style={{width: 400, height: 400}} /> + +// BAD +<Image source={{uri: 'https://facebook.github.io/react/img/logo_og.png'}} />

Local Filesystem Images #

See CameraRoll for an example of +using local resources that are outside of Images.xcassets.

Best Camera Roll Image #

iOS saves multiple sizes for the same image in your Camera Roll, it is very important to pick the one that's as close as possible for performance reasons. You wouldn't want to use the full quality 3264x2448 image as source when displaying a 200x200 thumbnail. If there's an exact match, React Native will pick it, otherwise it's going to use the first one that's at least 50% bigger in order to avoid blur when resizing from a close size. All of this is done by default so you don't have to worry about writing the tedious (and error prone) code to do it yourself.

Why Not Automatically Size Everything? #

In the browser if you don't give a size to an image, the browser is going to render a 0x0 element, download the image, and then render the image based with the correct size. The big issue with this behavior is that your UI is going to jump all around as images load, this makes for a very bad user experience.

In React Native this behavior is intentionally not implemented. It is more work for the developer to know the dimensions (or aspect ratio) of the remote image in advance, but we believe that it leads to a better user experience. Static images loaded from the app bundle via the require('./my-icon.png') syntax can be automatically sized because their dimensions are available immediately at the time of mounting.

For example, the result of require('./my-icon.png') might be:

{"__packager_asset":true,"uri":"my-icon.png","width":591,"height":573}

Source as an object #

In React Native, one interesting decision is that the src attribute is named source and doesn't take a string but an object with a uri attribute.

<Image source={{uri: 'something.jpg'}} />

On the infrastructure side, the reason is that it allows us to attach metadata to this object. For example if you are using require('./my-icon.png'), then we add information about its actual location and size (don't rely on this fact, it might change in the future!). This is also future proofing, for example we may want to support sprites at some point, instead of outputting {uri: ...}, we can output {uri: ..., crop: {left: 10, top: 50, width: 20, height: 40}} and transparently support spriting on all the existing call sites.

On the user side, this lets you annotate the object with useful attributes such as the dimension of the image in order to compute the size it's going to be displayed in. Feel free to use it as your data structure to store more information about your image.

Background Image via Nesting #

A common feature request from developers familiar with the web is background-image. To handle this use case, simply create a normal <Image> component and add whatever children to it you would like to layer on top of it.

return ( + <Image source={...}> + <Text>Inside</Text> + </Image> +);

Off-thread Decoding #

Image decoding can take more than a frame-worth of time. This is one of the major source of frame drops on the web because decoding is done in the main thread. In React Native, image decoding is done in a different thread. In practice, you already need to handle the case when the image is not downloaded yet, so displaying the placeholder for a few more frames while it is decoding does not require any code change.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/imagestore.html b/releases/0.37/docs/imagestore.html new file mode 100644 index 00000000000..9d60af82f4b --- /dev/null +++ b/releases/0.37/docs/imagestore.html @@ -0,0 +1,37 @@ +ImageStore

ImageStore #

Methods #

static hasImageForTag(uri, callback) #

Check if the ImageStore contains image data for the specified URI. +@platform ios

static removeImageForTag(uri) #

Delete an image from the ImageStore. Images are stored in memory and +must be manually removed when you are finished with them, otherwise they +will continue to use up RAM until the app is terminated. It is safe to +call removeImageForTag() without first calling hasImageForTag(), it +will simply fail silently. +@platform ios

static addImageFromBase64(base64ImageData, success, failure) #

Stores a base64-encoded image in the ImageStore, and returns a URI that +can be used to access or display the image later. Images are stored in +memory only, and must be manually deleted when you are finished with +them by calling removeImageForTag().

Note that it is very inefficient to transfer large quantities of binary +data between JS and native code, so you should avoid calling this more +than necessary. +@platform ios

static getBase64ForTag(uri, success, failure) #

Retrieves the base64-encoded data for an image in the ImageStore. If the +specified URI does not match an image in the store, the failure callback +will be called.

Note that it is very inefficient to transfer large quantities of binary +data between JS and native code, so you should avoid calling this more +than necessary. To display an image in the ImageStore, you can just pass +the URI to an <Image/> component; there is no need to retrieve the +base64 data.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/integration-with-existing-apps.html b/releases/0.37/docs/integration-with-existing-apps.html new file mode 100644 index 00000000000..7f0d985ce0d --- /dev/null +++ b/releases/0.37/docs/integration-with-existing-apps.html @@ -0,0 +1,450 @@ +Integration With Existing Apps

Integration With Existing Apps #

+ +Platform: +Objective-C +Swift +Android +
+ +
+ +

This section will be updated shortly showing an integration into a more real world application such as the 2048 app that was used for Objective-C and Swift.

+ +

Key Concepts #

React Native is great when you are starting a new mobile app from scratch. However, it also works well for adding a single view or user flow to existing native applications. With a few steps, you can add new React Native based features, screens, views, etc.

+ +

The keys to integrating React Native components into your iOS application are to:

  1. Understand what React Native components you want to integrate.
  2. Create a Podfile with subspecs for all the React Native components you will need for your integration.
  3. Create your actual React Native components in JavaScript.
  4. Add a new event handler that creates a RCTRootView that points to your React Native component and its AppRegistry name that you defined in index.ios.js.
  5. Start the React Native server and run your native application.
  6. Optionally add more React Native components.
  7. Debug.
  8. Prepare for deployment (e.g., via the react-native-xcode.sh script).
  9. Deploy and Profit!
+ +

The keys to integrating React Native components into your Android application are to:

  1. Understand what React Native components you want to integrate.
  2. Install react-native in your Android application root directory to create node_modules/ directory.
  3. Create your actual React Native components in JavaScript.
  4. Add com.facebook.react:react-native:+ and a maven pointing to the react-native binaries in node_modules/ to your build.gradle file.
  5. Create a custom React Native specific Activity that creates a ReactRootView.
  6. Start the React Native server and run your native application.
  7. Optionally add more React Native components.
  8. Debug.
  9. Prepare for deployment.
  10. Deploy and Profit!
+ +

Prerequisites #

+ +

The Android Getting Started guide will install the appropriate prerequisites (e.g., npm) for React Native on the Android target platform and your chosen development environment.

To ensure a smooth experience, make sure your android project is under $root/android.

+ +

General #

First, follow the Getting Started guide for your development environment and the iOS target platform to install the prerequisites for React Native.

To ensure a smooth experience, make sure your iOS project is under $root/ios.

CocoaPods #

CocoaPods is a package management tool for iOS and Mac development. We use it to add the actual React Native framework code locally into your current project.

$ sudo gem install cocoapods

It is technically possible not to use CocoaPods, but this requires manual library and linker additions that overly complicates this process.

Our Sample App #

+ +

Assume the app for integration is a <a href="https://en.wikipedia.org/wiki/2048_(video_game)">2048</a> game. Here is what the main menu of the native application looks like without React Native.

+ +

Assume the app for integration is a <a href="https://en.wikipedia.org/wiki/2048_(video_game)">2048</a> game. Here is what the main menu of the native application looks like without React Native.

+ +

Before RN Integration

Package Dependencies #

React Native integration requires both the React and React Native node modules. The React Native Framework will provide the code to allow your application integration to happen.

package.json #

We will add the package dependencies to a package.json file. Create this file in the root of your project if it does not exist.

Normally with React Native projects, you will put files like package.json, index.ios.js, etc. in the root directory of your project and then have your iOS specific native code in a subdirectory like ios/ where your Xcode project is located (e.g., .xcodeproj).

Below is an example of what your package.json file should minimally contain.

Version numbers will vary according to your needs. Normally the latest versions for both React and React Native will be sufficient.

+ +
{ + "name": "NumberTileGame", + "version": "0.0.1", + "private": true, + "scripts": { + "start": "node node_modules/react-native/local-cli/cli.js start" + }, + "dependencies": { + "react": "15.0.2", + "react-native": "0.26.1" + } +}
+ +
{ + "name": "swift-2048", + "version": "0.0.1", + "private": true, + "scripts": { + "start": "node node_modules/react-native/local-cli/cli.js start" + }, + "dependencies": { + "react": "15.0.2", + "react-native": "0.26.1" + } +}
+ +

Packages Installation #

Install the React and React Native modules via the Node package manager. The Node modules will be installed into a node_modules/ directory in the root of your project.

# From the directory containing package.json project, install the modules +# The modules will be installed in node_modules/ +$ npm install

React Native Framework #

The React Native Framework was installed as Node module in your project above. We will now install a CocoaPods Podfile with the components you want to use from the framework itself.

Subspecs #

Before you integrate React Native into your application, you will want to decide what parts of the React Native Framework you would like to integrate. That is where subspecs come in. When you create your Podfile, you are going to specify React Native library dependencies that you will want installed so that your application can use those libraries. Each library will become a subspec in the Podfile.

The list of supported subspecs are in node_modules/react-native/React.podspec. They are generally named by functionality. For example, you will generally always want the Core subspec. That will get you the AppRegistry, StyleSheet, View and other core React Native libraries. If you want to add the React Native Text library (e.g., for <Text> elements), then you will need the RCTText subspec. If you want the Image library (e.g., for <Image> elements), then you will need the RCTImage subspec.

Podfile #

After you have used Node to install the React and React Native frameworks into the node_modules directory, and you have decided on what React Native elements you want to integrate, you are ready to create your Podfile so you can install those components for use in your application.

The easiest way to create a Podfile is by using the CocoaPods init command in the native iOS code directory of your project:

## In the directory where your native iOS code is located (e.g., where your `.xcodeproj` file is located) +$ pod init

The Podfile will be created and saved in the iOS directory (e.g., ios/) of your current project and will contain a boilerplate setup that you will tweak for your integration purposes. In the end, Podfile should look something similar to this:

+ +
# The target name is most likely the name of your project. +target 'NumberTileGame' do + + # Your 'node_modules' directory is probably in the root of your project, + # but if not, adjust the `:path` accordingly + pod 'React', :path => '../node_modules/react-native', :subspecs => [ + 'Core', + 'RCTText', + 'RCTNetwork', + 'RCTWebSocket', # needed for debugging + # Add any other subspecs you want to use in your project + ] + +end
+ +
source 'https://github.com/CocoaPods/Specs.git' + +# Required for Swift apps +platform :ios, '8.0' +use_frameworks! + +# The target name is most likely the name of your project. +target 'swift-2048' do + + # Your 'node_modules' directory is probably in the root of your project, + # but if not, adjust the `:path` accordingly + pod 'React', :path => '../node_modules/react-native', :subspecs => [ + 'Core', + 'RCTText', + 'RCTNetwork', + 'RCTWebSocket', # needed for debugging + # Add any other subspecs you want to use in your project + ] + +end
+ +

Pod Installation #

After you have created your Podfile, you are ready to install the React Native pod.

$ pod install

Your should see output such as:

Analyzing dependencies +Fetching podspec for `React` from `../node_modules/react-native` +Downloading dependencies +Installing React (0.26.0) +Generating Pods project +Integrating client project +Sending stats +Pod installation complete! There are 3 dependencies from the Podfile and 1 total pod installed.
+ +

If you get a warning such as "The swift-2048 [Debug] target overrides the FRAMEWORK_SEARCH_PATHS build setting defined in Pods/Target Support Files/Pods-swift-2048/Pods-swift-2048.debug.xcconfig. This can lead to problems with the CocoaPods installation", then make sure the Framework Search Paths in Build Settings for both Debug and Release only contain $(inherited).

+ +

Code Integration #

Now that we have a package foundation, we will actually modify the native application to integrate React Native into the application. For our 2048 app, we will add a "High Score" screen in React Native.

The React Native component #

The first bit of code we will write is the actual React Native code for the new "High Score" screen that will be integrated into our application.

Create a index.ios.js file #

First, create an empty index.ios.js file. For ease, I am doing this in the root of the project.

index.ios.js is the starting point for React Native applications on iOS. And it is always required. It can be a small file that requires other file that are part of your React Native component or application, or it can contain all the code that is needed for it. In our case, we will just put everything in index.ios.js

# In root of your project +$ touch index.ios.js

Add Your React Native Code #

In your index.ios.js, create your component. In our sample here, we will add simple <Text> component within a styled <View>

'use strict'; + +import React from 'react'; +import { + AppRegistry, + StyleSheet, + Text, + View +} from 'react-native'; + +class RNHighScores extends React.Component { + render() { + var contents = this.props["scores"].map( + score => <Text key={score.name}>{score.name}:{score.value}{"\n"}</Text> + ); + return ( + <View style={styles.container}> + <Text style={styles.highScoresTitle}> + 2048 High Scores! + </Text> + <Text style={styles.scores}> + {contents} + </Text> + </View> + ); + } +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: '#FFFFFF', + }, + highScoresTitle: { + fontSize: 20, + textAlign: 'center', + margin: 10, + }, + scores: { + textAlign: 'center', + color: '#333333', + marginBottom: 5, + }, +}); + +// Module name +AppRegistry.registerComponent('RNHighScores', () => RNHighScores);

RNHighScores is the name of your module that will be used when you add a view to React Native from within your iOS application.

The Magic: RCTRootView #

Now that your React Native component is created via index.ios.js, you need to add that component to a new or existing ViewController. The easiest path to take is to optionally create an event path to your component and then add that component to an existing ViewController.

We will tie our React Native component with a new native view in the ViewController that will actually host it called RCTRootView .

Create an Event Path #

You can add a new link on the main game menu to go to the "High Score" React Native page.

Event Path

Event Handler #

We will now add an event handler from the menu link. A method will be added to the main ViewController of your application. This is where RCTRootView comes into play.

When you build a React Native application, you use the React Native packager to create an index.ios.bundle that will be served by the React Native server. Inside index.ios.bundle will be our RNHighScore module. So, we need to point our RCTRootView to the location of the index.ios.bundle resource (via NSURL) and tie it to the module.

We will, for debugging purposes, log that the event handler was invoked. Then, we will create a string with the location of our React Native code that exists inside the index.ios.bundle. Finally, we will create the main RCTRootView. Notice how we provide RNHighScores as the moduleName that we created above when writing the code for our React Native component.

+ +

First import the RCTRootView library.

#import "RCTRootView.h"

The initialProperties are here for illustration purposes so we have some data for our high score screen. In our React Native component, we will use this.props to get access to that data.

- (IBAction)highScoreButtonPressed:(id)sender { + NSLog(@"High Score Button Pressed"); + NSURL *jsCodeLocation = [NSURL + URLWithString:@"http://localhost:8081/index.ios.bundle?platform=ios"]; + RCTRootView *rootView = + [[RCTRootView alloc] initWithBundleURL : jsCodeLocation + moduleName : @"RNHighScores" + initialProperties : + @{ + @"scores" : @[ + @{ + @"name" : @"Alex", + @"value": @"42" + }, + @{ + @"name" : @"Joel", + @"value": @"10" + } + ] + } + launchOptions : nil]; + UIViewController *vc = [[UIViewController alloc] init]; + vc.view = rootView; + [self presentViewController:vc animated:YES completion:nil]; +}

Note that RCTRootView initWithURL starts up a new JSC VM. To save resources and simplify the communication between RN views in different parts of your native app, you can have multiple views powered by React Native that are associated with a single JS runtime. To do that, instead of using [RCTRootView alloc] initWithURL, use RCTBridge initWithBundleURL to create a bridge and then use RCTRootView initWithBridge.

+ +

First import the React library.

import React

The initialProperties are here for illustration purposes so we have some data for our high score screen. In our React Native component, we will use this.props to get access to that data.

@IBAction func highScoreButtonTapped(sender : UIButton) { + NSLog("Hello") + let jsCodeLocation = NSURL(string: "http://localhost:8081/index.ios.bundle?platform=ios") + let mockData:NSDictionary = ["scores": + [ + ["name":"Alex", "value":"42"], + ["name":"Joel", "value":"10"] + ] + ] + + let rootView = RCTRootView( + bundleURL: jsCodeLocation, + moduleName: "RNHighScores", + initialProperties: mockData as [NSObject : AnyObject], + launchOptions: nil + ) + let vc = UIViewController() + vc.view = rootView + self.presentViewController(vc, animated: true, completion: nil) +}

Note that RCTRootView bundleURL starts up a new JSC VM. To save resources and simplify the communication between RN views in different parts of your native app, you can have multiple views powered by React Native that are associated with a single JS runtime. To do that, instead of using RCTRootView bundleURL, use RCTBridge initWithBundleURL to create a bridge and then use RCTRootView initWithBridge.

+ +

When moving your app to production, the NSURL can point to a pre-bundled file on disk via something like [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];. You can use the react-native-xcode.sh script in node_modules/react-native/packager/ to generate that pre-bundled file.

+ +

When moving your app to production, the NSURL can point to a pre-bundled file on disk via something like let mainBundle = NSBundle(URLForResource: "main" withExtension:"jsbundle"). You can use the react-native-xcode.sh script in node_modules/react-native/packager/ to generate that pre-bundled file.

+ +

Wire Up #

Wire up the new link in the main menu to the newly added event handler method.

Event Path

One of the easier ways to do this is to open the view in the storyboard and right click on the new link. Select something such as the Touch Up Inside event, drag that to the storyboard and then select the created method from the list provided.

Test Your Integration #

You have now done all the basic steps to integrate React Native with your current application. Now we will start the React Native packager to build the index.ios.bundle packager and the server running on localhost to serve it.

App Transport Security #

Apple has blocked implicit cleartext HTTP resource loading. So we need to add the following our project's Info.plist (or equivalent) file.

<key>NSAppTransportSecurity</key> +<dict> + <key>NSExceptionDomains</key> + <dict> + <key>localhost</key> + <dict> + <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key> + <true/> + </dict> + </dict> +</dict>

Run the Packager #

# From the root of your project, where the `node_modules` directory is located. +$ npm start

Run the App #

If you are using Xcode or your favorite editor, build and run your native iOS application as normal. Alternatively, you can run the app from the command line using:

# From the root of your project +$ react-native run-ios

In our sample application, you should see the link to the "High Scores" and then when you click on that you will see the rendering of your React Native component.

Here is the native application home screen:

Home Screen

Here is the React Native high score screen:

High Scores

If you are getting module resolution issues when running your application please see this GitHub issue for information and possible resolution. This comment seemed to be the latest possible resolution.

See the Code #

+ +

You can examine the code that added the React Native screen on GitHub.

+ +

You can examine the code that added the React Native screen on GitHub.

+ +

Add JS to your app #

In your app's root folder, run:

$ npm init +$ npm install --save react react-native +$ curl -o .flowconfig https://raw.githubusercontent.com/facebook/react-native/master/.flowconfig

This creates a node module for your app and adds the react-native npm dependency. Now open the newly created package.json file and add this under scripts:

"start": "node node_modules/react-native/local-cli/cli.js start"

Copy & paste the following code to index.android.js in your root folder — it's a barebones React Native app:

'use strict'; + +import React from 'react'; +import { + AppRegistry, + StyleSheet, + Text, + View +} from 'react-native'; + +class HelloWorld extends React.Component { + render() { + return ( + <View style={styles.container}> + <Text style={styles.hello}>Hello, World</Text> + </View> + ) + } +} +var styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + }, + hello: { + fontSize: 20, + textAlign: 'center', + margin: 10, + }, +}); + +AppRegistry.registerComponent('HelloWorld', () => HelloWorld);

Prepare your current app #

In your app's build.gradle file add the React Native dependency:

dependencies { + ... + compile "com.facebook.react:react-native:+" // From node_modules. +}

If you want to ensure that you are always using a specific React Native version in your native build, replace + with an actual React Native version you've downloaded from npm.

In your project's build.gradle file add an entry for the local React Native maven directory:

allprojects { + repositories { + ... + maven { + // All of React Native (JS, Android binaries) is installed from npm + url "$rootDir/../node_modules/react-native/android" + } + } + ... +}

Make sure that the path is correct! You shouldn’t run into any “Failed to resolve: com.facebook.react:react-native:0.x.x" errors after running Gradle sync in Android Studio.

Next, make sure you have the Internet permission in your AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET" />

If you need to access to the DevSettingsActivity add to your AndroidManifest.xml:

<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />

This is only really used in dev mode when reloading JavaScript from the development server, so you can strip this in release builds if you need to.

Add native code #

You need to add some native code in order to start the React Native runtime and get it to render something. To do this, we're going to create an Activity that creates a ReactRootView, starts a React application inside it and sets it as the main content view.

If you are targetting Android version <5, use the AppCompatActivity class from the com.android.support:appcompat package instead of Activity.

public class MyReactActivity extends Activity implements DefaultHardwareBackBtnHandler { + private ReactRootView mReactRootView; + private ReactInstanceManager mReactInstanceManager; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mReactRootView = new ReactRootView(this); + mReactInstanceManager = ReactInstanceManager.builder() + .setApplication(getApplication()) + .setBundleAssetName("index.android.bundle") + .setJSMainModuleName("index.android") + .addPackage(new MainReactPackage()) + .setUseDeveloperSupport(BuildConfig.DEBUG) + .setInitialLifecycleState(LifecycleState.RESUMED) + .build(); + mReactRootView.startReactApplication(mReactInstanceManager, "HelloWorld", null); + + setContentView(mReactRootView); + } + + @Override + public void invokeDefaultOnBackPressed() { + super.onBackPressed(); + } +}

If you are using a starter kit for React Native, replace the "HelloWorld" string with the one in your index.android.js file (it’s the first argument to the AppRegistry.registerComponent() method).

If you are using Android Studio, use Alt + Enter to add all missing imports in your MyReactActivity class. Be careful to use your package’s BuildConfig and not the one from the ...facebook... package.

We need set the theme of MyReactActivity to Theme.AppCompat.Light.NoActionBar because some components rely on this theme.

<activity + android:name=".MyReactActivity" + android:label="@string/app_name" + android:theme="@style/Theme.AppCompat.Light.NoActionBar"> +</activity>

A ReactInstanceManager can be shared amongst multiple activities and/or fragments. You will want to make your own ReactFragment or ReactActivity and have a singleton holder that holds a ReactInstanceManager. When you need the ReactInstanceManager (e.g., to hook up the ReactInstanceManager to the lifecycle of those Activities or Fragments) use the one provided by the singleton.

Next, we need to pass some activity lifecycle callbacks down to the ReactInstanceManager:

@Override +protected void onPause() { + super.onPause(); + + if (mReactInstanceManager != null) { + mReactInstanceManager.onPause(this); + } +} + +@Override +protected void onResume() { + super.onResume(); + + if (mReactInstanceManager != null) { + mReactInstanceManager.onResume(this, this); + } +} + +@Override +protected void onDestroy() { + super.onDestroy(); + + if (mReactInstanceManager != null) { + mReactInstanceManager.onDestroy(this); + } +}

We also need to pass back button events to React Native:

@Override + public void onBackPressed() { + if (mReactInstanceManager != null) { + mReactInstanceManager.onBackPressed(); + } else { + super.onBackPressed(); + } +}

This allows JavaScript to control what happens when the user presses the hardware back button (e.g. to implement navigation). When JavaScript doesn't handle a back press, your invokeDefaultOnBackPressed method will be called. By default this simply finishes your Activity.

Finally, we need to hook up the dev menu. By default, this is activated by (rage) shaking the device, but this is not very useful in emulators. So we make it show when you press the hardware menu button (use Ctrl + M if you're using Android Studio emulator):

@Override +public boolean onKeyUp(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) { + mReactInstanceManager.showDevOptionsDialog(); + return true; + } + return super.onKeyUp(keyCode, event); +}

That's it, your activity is ready to run some JavaScript code.

Run your app #

To run your app, you need to first start the development server. To do this, simply run the following command in your root folder:

$ npm start

Now build and run your Android app as normal (./gradlew installDebug from command-line; in Android Studio just create debug build as usual).

If you are using Android Studio for your builds and not the Gradle Wrapper directly, make sure you install watchman before running npm start. It will prevent the packager from crashing due to conflicts between Android Studio and the React Native packager.

Once you reach your React-powered activity inside the app, it should load the JavaScript code from the development server and display:

Screenshot

+ +

Creating a release build in Android Studio #

You can use Android Studio to create your release builds too! It’s as easy as creating release builds of your previously-existing native Android app. There’s just one additional step, which you’ll have to do before every release build. You need to execute the following to create a React Native bundle, which’ll be included with your native Android app:

$ react-native bundle --platform android --dev false --entry-file index.android.js --bundle-output android/com/your-company-name/app-package-name/src/main/assets/index.android.bundle --assets-dest android/com/your-company-name/app-package-name/src/main/res/

Don’t forget to replace the paths with correct ones and create the assets folder if it doesn’t exist!

Now just create a release build of your native app from within Android Studio as usual and you should be good to go!

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/interactionmanager.html b/releases/0.37/docs/interactionmanager.html new file mode 100644 index 00000000000..efb16744629 --- /dev/null +++ b/releases/0.37/docs/interactionmanager.html @@ -0,0 +1,44 @@ +InteractionManager

InteractionManager #

InteractionManager allows long-running work to be scheduled after any +interactions/animations have completed. In particular, this allows JavaScript +animations to run smoothly.

Applications can schedule tasks to run after interactions with the following:

InteractionManager.runAfterInteractions(() => { + // ...long-running synchronous task... +});

Compare this to other scheduling alternatives:

  • requestAnimationFrame(): for code that animates a view over time.
  • setImmediate/setTimeout(): run code later, note this may delay animations.
  • runAfterInteractions(): run code later, without delaying active animations.

The touch handling system considers one or more active touches to be an +'interaction' and will delay runAfterInteractions() callbacks until all +touches have ended or been cancelled.

InteractionManager also allows applications to register animations by +creating an interaction 'handle' on animation start, and clearing it upon +completion:

var handle = InteractionManager.createInteractionHandle(); +// run animation... (`runAfterInteractions` tasks are queued) +// later, on animation completion: +InteractionManager.clearInteractionHandle(handle); +// queued tasks run if all handles were cleared

runAfterInteractions takes either a plain callback function, or a +PromiseTask object with a gen method that returns a Promise. If a +PromiseTask is supplied, then it is fully resolved (including asynchronous +dependencies that also schedule more tasks via runAfterInteractions) before +starting on the next task that might have been queued up synchronously +earlier.

By default, queued tasks are executed together in a loop in one +setImmediate batch. If setDeadline is called with a positive number, then +tasks will only be executed until the deadline (in terms of js event loop run +time) approaches, at which point execution will yield via setTimeout, +allowing events such as touches to start interactions and block queued tasks +from executing, making apps more responsive.

Methods #

static runAfterInteractions(task) #

Schedule a function to run after all interactions have completed. Returns a cancellable +"promise".

static createInteractionHandle(0) #

Notify manager that an interaction has started.

static clearInteractionHandle(handle) #

Notify manager that an interaction has completed.

static setDeadline(deadline) #

A positive number will use setTimeout to schedule any tasks after the +eventLoopRunningTime hits the deadline value, otherwise all tasks will be +executed in one setImmediate batch (default).

Properties #

Events: CallExpression #

addListener: CallExpression #

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/javascript-environment.html b/releases/0.37/docs/javascript-environment.html new file mode 100644 index 00000000000..6665f6072a5 --- /dev/null +++ b/releases/0.37/docs/javascript-environment.html @@ -0,0 +1,19 @@ +JavaScript Environment

JavaScript Environment #

JavaScript Runtime #

When using React Native, you're going to be running your JavaScript code in two environments:

  • On iOS simulators and devices, Android emulators and devices React Native uses JavaScriptCore which is the JavaScript engine that powers Safari. On iOS JSC doesn't use JIT due to the absence of writable executable memory in iOS apps.
  • When using Chrome debugging, it runs all the JavaScript code within Chrome itself and communicates with native code via WebSocket. So you are using V8.

While both environments are very similar, you may end up hitting some inconsistencies. We're likely going to experiment with other JS engines in the future, so it's best to avoid relying on specifics of any runtime.

JavaScript Syntax Transformers #

Syntax transformers make writing code more enjoyable by allowing you to use new JavaScript syntax without having to wait for support on all interpreters.

As of version 0.5.0, React Native ships with the Babel JavaScript compiler. Check Babel documentation on its supported transformations for more details.

Here's a full list of React Native's enabled transformations.

ES5

  • Reserved Words: promise.catch(function() { });

ES6

ES7

Specific

  • JSX: <View style={{color: 'red'}} />
  • Flow: function foo(x: ?number): string {}

Polyfills #

Many standards functions are also available on all the supported JavaScript runtimes.

Browser

ES6

ES7

Specific

  • __DEV__

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/keyboard.html b/releases/0.37/docs/keyboard.html new file mode 100644 index 00000000000..65b91b10e9b --- /dev/null +++ b/releases/0.37/docs/keyboard.html @@ -0,0 +1,57 @@ +Keyboard

Keyboard #

Keyboard component to control keyboard events.

Usage #

The Keyboard component allows you to listen for native events and react to them, as +well as make changes to the keyboard, like dismissing it.

import React, { Component } from 'react'; +import { Keyboard, TextInput } from 'react-native'; + +class Example extends Component { + componentWillMount () { + this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this._keyboardDidShow); + this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this._keyboardDidHide); + } + + componentWillUnmount () { + this.keyboardDidShowListener.remove(); + this.keyboardDidHideListener.remove(); + } + + _keyboardDidShow () { + alert('Keyboard Shown'); + } + + _keyboardDidHide () { + alert('Keyboard Hidden'); + } + + render() { + return ( + <TextInput + onSubmitEditing={Keyboard.dismiss} + /> + ); + } +}

Methods #

static addListener(nativeEvent, jsFunction) #

The addListener function connects a JavaScript function to an identified native +keyboard notification event.

This function then returns the reference to the listener.

@param {string} nativeEvent The nativeEvent is the string that identifies the event you're listening for. This +can be any of the following: +- keyboardWillShow +- keyboardDidShow +- keyboardWillHide +- keyboardDidHide +- keyboardWillChangeFrame +- keyboardDidChangeFrame

@param {function} jsFunction function to be called when the event fires.

static removeAllListeners(eventType) #

Removes all listeners for a specific event type.

@param {string} eventType The native event string listeners are watching which will be removed.

static removeSubscription(subscription) #

Removes a specific subscription.

@param {EmitterSubscription} subscription The subscription emitter to be removed.

static dismiss(0) #

Dismisses the active keyboard and removes focus.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/keyboardavoidingview.html b/releases/0.37/docs/keyboardavoidingview.html new file mode 100644 index 00000000000..3ae4584668d --- /dev/null +++ b/releases/0.37/docs/keyboardavoidingview.html @@ -0,0 +1,117 @@ +KeyboardAvoidingView

KeyboardAvoidingView #

It is a component to solve the common problem of views that need to move out of the way of the virtual keyboard. +It can automatically adjust either its position or bottom padding based on the position of the keyboard.

Props #

behavior enum('height', 'position', 'padding') #

contentContainerStyle View#style #

The style of the content container(View) when behavior is 'position'.

keyboardVerticalOffset number #

This is the distance between the top of the user screen and the react native view, +may be non-zero in some use cases.

Methods #

relativeKeyboardHeight(keyboardFrame): #

onKeyboardChange(event) #

onLayout(event) #

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +const React = require('React'); +const ReactNative = require('react-native'); +const { + KeyboardAvoidingView, + Modal, + SegmentedControlIOS, + StyleSheet, + Text, + TextInput, + TouchableHighlight, + View, +} = ReactNative; + +const UIExplorerBlock = require('./UIExplorerBlock'); +const UIExplorerPage = require('./UIExplorerPage'); + +class KeyboardAvoidingViewExample extends React.Component { + static title = '<KeyboardAvoidingView>'; + static description = 'Base component for views that automatically adjust their height or position to move out of the way of the keyboard.'; + + state = { + behavior: 'padding', + modalOpen: false, + }; + + onSegmentChange = (segment: String) => { + this.setState({behavior: segment.toLowerCase()}); + }; + + renderExample = () => { + return ( + <View style={styles.outerContainer}> + <Modal animationType="fade" visible={this.state.modalOpen}> + <KeyboardAvoidingView behavior={this.state.behavior} style={styles.container}> + <SegmentedControlIOS + onValueChange={this.onSegmentChange} + selectedIndex={this.state.behavior === 'padding' ? 0 : 1} + style={styles.segment} + values={['Padding', 'Position']} /> + <TextInput + placeholder="<TextInput />" + style={styles.textInput} /> + </KeyboardAvoidingView> + <TouchableHighlight + onPress={() => this.setState({modalOpen: false})} + style={styles.closeButton}> + <Text>Close</Text> + </TouchableHighlight> + </Modal> + + <TouchableHighlight onPress={() => this.setState({modalOpen: true})}> + <Text>Open Example</Text> + </TouchableHighlight> + </View> + ); + }; + + render() { + return ( + <UIExplorerPage title="Keyboard Avoiding View"> + <UIExplorerBlock title="Keyboard-avoiding views move out of the way of the keyboard."> + {this.renderExample()} + </UIExplorerBlock> + </UIExplorerPage> + ); + } +} + +const styles = StyleSheet.create({ + outerContainer: { + flex: 1, + }, + container: { + flex: 1, + justifyContent: 'center', + paddingHorizontal: 20, + paddingTop: 20, + }, + textInput: { + borderRadius: 5, + borderWidth: 1, + height: 44, + paddingHorizontal: 10, + }, + segment: { + marginBottom: 10, + }, + closeButton: { + position: 'absolute', + top: 30, + left: 10, + } +}); + +module.exports = KeyboardAvoidingViewExample;
\ No newline at end of file diff --git a/releases/0.37/docs/layout-props.html b/releases/0.37/docs/layout-props.html new file mode 100644 index 00000000000..b5545fe8948 --- /dev/null +++ b/releases/0.37/docs/layout-props.html @@ -0,0 +1,131 @@ +Layout Props

Layout Props #

Props #

alignItems enum('flex-start', 'flex-end', 'center', 'stretch') #

alignItems aligns children in the cross direction. + For example, if children are flowing vertically, alignItems + controls how they align horizontally. + It works like align-items in CSS (default: stretch). + See https://developer.mozilla.org/en-US/docs/Web/CSS/align-items + for more details.

alignSelf enum('auto', 'flex-start', 'flex-end', 'center', 'stretch') #

alignSelf controls how a child aligns in the cross direction, + overriding the alignItems of the parent. It works like align-self + in CSS (default: auto). + See https://developer.mozilla.org/en-US/docs/Web/CSS/align-self + for more details.

borderBottomWidth number #

borderBottomWidth works like border-bottom-width in CSS. +See https://developer.mozilla.org/en-US/docs/Web/CSS/border-bottom-width +for more details.

borderLeftWidth number #

borderLeftWidth works like border-left-width in CSS. +See https://developer.mozilla.org/en-US/docs/Web/CSS/border-left-width +for more details.

borderRightWidth number #

borderRightWidth works like border-right-width in CSS. +See https://developer.mozilla.org/en-US/docs/Web/CSS/border-right-width +for more details.

borderTopWidth number #

borderTopWidth works like border-top-width in CSS. +See https://developer.mozilla.org/en-US/docs/Web/CSS/border-top-width +for more details.

borderWidth number #

borderWidth works like border-width in CSS. +See https://developer.mozilla.org/en-US/docs/Web/CSS/border-width +for more details.

bottom number #

bottom is the number of logical pixels to offset the bottom edge of + this component.

It works similarly to bottom in CSS, but in React Native you must + use logical pixel units, rather than percents, ems, or any of that.

See https://developer.mozilla.org/en-US/docs/Web/CSS/bottom + for more details of how bottom affects layout.

flex number #

In React Native flex does not work the same way that it does in CSS. + flex is a number rather than a string, and it works + according to the css-layout library + at https://github.com/facebook/css-layout.

When flex is a positive number, it makes the component flexible + and it will be sized proportional to its flex value. So a + component with flex set to 2 will take twice the space as a + component with flex set to 1.

When flex is 0, the component is sized according to width + and height and it is inflexible.

When flex is -1, the component is normally sized according + width and height. However, if there's not enough space, + the component will shrink to its minWidth and minHeight.

flexGrow, flexShrink, and flexBasis work the same as in CSS.

flexBasis number #

flexDirection enum('row', 'row-reverse', 'column', 'column-reverse') #

flexDirection controls which directions children of a container go. + row goes left to right, column goes top to bottom, and you may + be able to guess what the other two do. It works like flex-direction + in CSS, except the default is column. + See https://developer.mozilla.org/en-US/docs/Web/CSS/flex-direction + for more details.

flexGrow number #

flexShrink number #

flexWrap enum('wrap', 'nowrap') #

flexWrap controls whether children can wrap around after they + hit the end of a flex container. + It works like flex-wrap in CSS (default: nowrap). + See https://developer.mozilla.org/en-US/docs/Web/CSS/flex-wrap + for more details.

height number #

height sets the height of this component.

It works similarly to height in CSS, but in React Native you + must use logical pixel units, rather than percents, ems, or any of that. + See https://developer.mozilla.org/en-US/docs/Web/CSS/height for more details.

justifyContent enum('flex-start', 'flex-end', 'center', 'space-between', 'space-around') #

justifyContent aligns children in the main direction. + For example, if children are flowing vertically, justifyContent + controls how they align vertically. + It works like justify-content in CSS (default: flex-start). + See https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content + for more details.

left number #

left is the number of logical pixels to offset the left edge of + this component.

It works similarly to left in CSS, but in React Native you must + use logical pixel units, rather than percents, ems, or any of that.

See https://developer.mozilla.org/en-US/docs/Web/CSS/left + for more details of how left affects layout.

margin number #

Setting margin has the same effect as setting each of + marginTop, marginLeft, marginBottom, and marginRight. + See https://developer.mozilla.org/en-US/docs/Web/CSS/margin + for more details.

marginBottom number #

marginBottom works like margin-bottom in CSS. + See https://developer.mozilla.org/en-US/docs/Web/CSS/margin-bottom + for more details.

marginHorizontal number #

Setting marginHorizontal has the same effect as setting + both marginLeft and marginRight.

marginLeft number #

marginLeft works like margin-left in CSS. + See https://developer.mozilla.org/en-US/docs/Web/CSS/margin-left + for more details.

marginRight number #

marginRight works like margin-right in CSS. + See https://developer.mozilla.org/en-US/docs/Web/CSS/margin-right + for more details.

marginTop number #

marginTop works like margin-top in CSS. + See https://developer.mozilla.org/en-US/docs/Web/CSS/margin-top + for more details.

marginVertical number #

Setting marginVertical has the same effect as setting both + marginTop and marginBottom.

maxHeight number #

maxHeight is the maximum height for this component, in logical pixels.

It works similarly to max-height in CSS, but in React Native you + must use logical pixel units, rather than percents, ems, or any of that.

See https://developer.mozilla.org/en-US/docs/Web/CSS/max-height + for more details.

maxWidth number #

maxWidth is the maximum width for this component, in logical pixels.

It works similarly to max-width in CSS, but in React Native you + must use logical pixel units, rather than percents, ems, or any of that.

See https://developer.mozilla.org/en-US/docs/Web/CSS/max-width + for more details.

minHeight number #

minHeight is the minimum height for this component, in logical pixels.

It works similarly to min-height in CSS, but in React Native you + must use logical pixel units, rather than percents, ems, or any of that.

See https://developer.mozilla.org/en-US/docs/Web/CSS/min-height + for more details.

minWidth number #

minWidth is the minimum width for this component, in logical pixels.

It works similarly to min-width in CSS, but in React Native you + must use logical pixel units, rather than percents, ems, or any of that.

See https://developer.mozilla.org/en-US/docs/Web/CSS/min-width + for more details.

overflow enum('visible', 'hidden', 'scroll') #

overflow controls how a children are measured and displayed. + overflow: hidden causes views to be clipped while overflow: scroll + causes views to be measured independently of their parents main axis.It works likeoverflow` in CSS (default: visible). + See https://developer.mozilla.org/en/docs/Web/CSS/overflow + for more details.

padding number #

Setting padding has the same effect as setting each of + paddingTop, paddingBottom, paddingLeft, and paddingRight. + See https://developer.mozilla.org/en-US/docs/Web/CSS/padding + for more details.

paddingBottom number #

paddingBottom works like padding-bottom in CSS. +See https://developer.mozilla.org/en-US/docs/Web/CSS/padding-bottom +for more details.

paddingHorizontal number #

Setting paddingHorizontal is like setting both of + paddingLeft and paddingRight.

paddingLeft number #

paddingLeft works like padding-left in CSS. +See https://developer.mozilla.org/en-US/docs/Web/CSS/padding-left +for more details.

paddingRight number #

paddingRight works like padding-right in CSS. +See https://developer.mozilla.org/en-US/docs/Web/CSS/padding-right +for more details.

paddingTop number #

paddingTop works like padding-top in CSS. +See https://developer.mozilla.org/en-US/docs/Web/CSS/padding-top +for more details.

paddingVertical number #

Setting paddingVertical is like setting both of + paddingTop and paddingBottom.

position enum('absolute', 'relative') #

position in React Native is similar to regular CSS, but + everything is set to relative by default, so absolute + positioning is always just relative to the parent.

If you want to position a child using specific numbers of logical + pixels relative to its parent, set the child to have absolute + position.

If you want to position a child relative to something + that is not its parent, just don't use styles for that. Use the + component tree.

See https://github.com/facebook/css-layout + for more details on how position differs between React Native + and CSS.

right number #

right is the number of logical pixels to offset the right edge of + this component.

It works similarly to right in CSS, but in React Native you must + use logical pixel units, rather than percents, ems, or any of that.

See https://developer.mozilla.org/en-US/docs/Web/CSS/right + for more details of how right affects layout.

top number #

top is the number of logical pixels to offset the top edge of + this component.

It works similarly to top in CSS, but in React Native you must + use logical pixel units, rather than percents, ems, or any of that.

See https://developer.mozilla.org/en-US/docs/Web/CSS/top + for more details of how top affects layout.

width number #

width sets the width of this component.

It works similarly to width in CSS, but in React Native you + must use logical pixel units, rather than percents, ems, or any of that. + See https://developer.mozilla.org/en-US/docs/Web/CSS/width for more details.

zIndex number #

zIndex controls which components display on top of others. + Normally, you don't use zIndex. Components render according to + their order in the document tree, so later components draw over + earlier ones. zIndex may be useful if you have animations or custom + modal interfaces where you don't want this behavior.

It works like the CSS z-index property - components with a larger + zIndex will render on top. Think of the z-direction like it's + pointing from the phone into your eyeball. + See https://developer.mozilla.org/en-US/docs/Web/CSS/z-index for + more details.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/layoutanimation.html b/releases/0.37/docs/layoutanimation.html new file mode 100644 index 00000000000..66ced6272c2 --- /dev/null +++ b/releases/0.37/docs/layoutanimation.html @@ -0,0 +1,173 @@ +LayoutAnimation

LayoutAnimation #

Automatically animates views to their new positions when the +next layout happens.

A common way to use this API is to call LayoutAnimation.configureNext +before calling setState.

Methods #

static configureNext(config, onAnimationDidEnd?) #

Schedules an animation to happen on the next layout.

@param config Specifies animation properties:

  • duration in milliseconds
  • create, config for animating in new views (see Anim type)
  • update, config for animating views that have been updated +(see Anim type)

@param onAnimationDidEnd Called when the animation finished. +Only supported on iOS. +@param onError Called on error. Only supported on iOS.

static create(duration, type, creationProp) #

Helper for creating a config for configureNext.

Properties #

Types: CallExpression #

Properties: CallExpression #

configChecker: CallExpression #

Presets: ObjectExpression #

easeInEaseOut: CallExpression #

linear: CallExpression #

spring: CallExpression #

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +const React = require('react'); +const ReactNative = require('react-native'); +const { + LayoutAnimation, + StyleSheet, + Text, + View, + TouchableOpacity, +} = ReactNative; + +class AddRemoveExample extends React.Component { + state = { + views: [], + }; + + componentWillUpdate() { + LayoutAnimation.easeInEaseOut(); + } + + _onPressAddView = () => { + this.setState((state) => ({views: [...state.views, {}]})); + }; + + _onPressRemoveView = () => { + this.setState((state) => ({views: state.views.slice(0, -1)})); + }; + + render() { + const views = this.state.views.map((view, i) => + <View key={i} style={styles.view}> + <Text>{i}</Text> + </View> + ); + return ( + <View style={styles.container}> + <TouchableOpacity onPress={this._onPressAddView}> + <View style={styles.button}> + <Text>Add view</Text> + </View> + </TouchableOpacity> + <TouchableOpacity onPress={this._onPressRemoveView}> + <View style={styles.button}> + <Text>Remove view</Text> + </View> + </TouchableOpacity> + <View style={styles.viewContainer}> + {views} + </View> + </View> + ); + } +} + +const GreenSquare = () => + <View style={styles.greenSquare}> + <Text>Green square</Text> + </View>; + +const BlueSquare = () => + <View style={styles.blueSquare}> + <Text>Blue square</Text> + </View>; + +class CrossFadeExample extends React.Component { + state = { + toggled: false, + }; + + _onPressToggle = () => { + LayoutAnimation.easeInEaseOut(); + this.setState((state) => ({toggled: !state.toggled})); + }; + + render() { + return ( + <View style={styles.container}> + <TouchableOpacity onPress={this._onPressToggle}> + <View style={styles.button}> + <Text>Toggle</Text> + </View> + </TouchableOpacity> + <View style={styles.viewContainer}> + { + this.state.toggled ? + <GreenSquare /> : + <BlueSquare /> + } + </View> + </View> + ); + } +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + button: { + borderRadius: 5, + backgroundColor: '#eeeeee', + padding: 10, + marginBottom: 10, + }, + buttonText: { + fontSize: 16, + }, + viewContainer: { + flex: 1, + flexDirection: 'row', + flexWrap: 'wrap', + }, + view: { + height: 54, + width: 54, + backgroundColor: 'red', + margin: 8, + alignItems: 'center', + justifyContent: 'center', + }, + greenSquare: { + width: 150, + height: 150, + backgroundColor: 'green', + alignItems: 'center', + justifyContent: 'center', + }, + blueSquare: { + width: 150, + height: 150, + backgroundColor: 'blue', + alignItems: 'center', + justifyContent: 'center', + }, +}); + +exports.title = 'Layout Animation'; +exports.description = 'Layout animation'; +exports.examples = [{ + title: 'Add and remove views', + render(): React.Element<any> { + return <AddRemoveExample />; + }, +}, { + title: 'Cross fade views', + render(): React.Element<any> { + return <CrossFadeExample />; + }, +}];
\ No newline at end of file diff --git a/releases/0.37/docs/linking-libraries-ios.html b/releases/0.37/docs/linking-libraries-ios.html new file mode 100644 index 00000000000..29ea883641d --- /dev/null +++ b/releases/0.37/docs/linking-libraries-ios.html @@ -0,0 +1,40 @@ +Linking Libraries

Linking Libraries #

Not every app uses all the native capabilities, and including the code to support +all those features would impact the binary size... But we still want to make it +easy to add these features whenever you need them.

With that in mind we exposed many of these features as independent static libraries.

For most of the libs it will be as simple as dragging two files, sometimes a third +step will be necessary, but no more than that.

All the libraries we ship with React Native live on the Libraries folder in +the root of the repository. Some of them are pure JavaScript, and you only need +to require it. Other libraries also rely on some native code, in that case +you'll have to add these files to your app, otherwise the app will throw an +error as soon as you try to use the library.

Here the few steps to link your libraries that contain native code #

Automatic linking #

Step 1 #

Install a library with native dependencies:

$ npm install <library-with-native-dependencies> --save

Note: --save or --save-dev flag is very important for this step. React Native will link +your libs based on dependencies and devDependencies in your package.json file.

Step 2 #

Link your native dependencies:

$ react-native link

Done! All libraries with a native dependencies should be successfully linked to your iOS/Android project.

Manual linking #

Step 1 #

If the library has native code, there must be a .xcodeproj file inside it's +folder. +Drag this file to your project on Xcode (usually under the Libraries group +on Xcode);

Step 2 #

Click on your main project file (the one that represents the .xcodeproj) +select Build Phases and drag the static library from the Products folder +inside the Library you are importing to Link Binary With Libraries

Step 3 #

Not every library will need this step, what you need to consider is:

Do I need to know the contents of the library at compile time?

What that means is, are you using this library on the native side or only in +JavaScript? If you are only using it in JavaScript, you are good to go!

This step is not necessary for libraries that we ship with React Native with the +exception of PushNotificationIOS and Linking.

In the case of the PushNotificationIOS for example, you have to call a method +on the library from your AppDelegate every time a new push notification is +received.

For that we need to know the library's headers. To achieve that you have to go +to your project's file, select Build Settings and search for Header Search +Paths. There you should include the path to your library (if it has relevant +files on subdirectories remember to make it recursive, like React on the +example).

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/linking.html b/releases/0.37/docs/linking.html new file mode 100644 index 00000000000..30d409dee79 --- /dev/null +++ b/releases/0.37/docs/linking.html @@ -0,0 +1,147 @@ +Linking

Linking #

Linking gives you a general interface to interact with both incoming +and outgoing app links.

Basic Usage #

Handling deep links #

If your app was launched from an external url registered to your app you can +access and handle it from any component you want with

componentDidMount() { + var url = Linking.getInitialURL().then((url) => { + if (url) { + console.log('Initial url is: ' + url); + } + }).catch(err => console.error('An error occurred', err)); +}

NOTE: For instructions on how to add support for deep linking on Android, +refer to Enabling Deep Links for App Content - Add Intent Filters for Your Deep Links.

If you wish to receive the intent in an existing instance of MainActivity, +you may set the launchMode of MainActivity to singleTask in +AndroidManifest.xml. See <activity> +documentation for more information.

<activity + android:name=".MainActivity" + android:launchMode="singleTask">

NOTE: On iOS you'll need to link RCTLinking to your project by following +the steps described here. +In case you also want to listen to incoming app links during your app's +execution you'll need to add the following lines to you *AppDelegate.m:

#import "RCTLinkingManager.h" + +- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url + sourceApplication:(NSString *)sourceApplication annotation:(id)annotation +{ + return [RCTLinkingManager application:application openURL:url + sourceApplication:sourceApplication annotation:annotation]; +} + +// Only if your app is using [Universal Links](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/UniversalLinks.html). +- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity + restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler +{ + return [RCTLinkingManager application:application + continueUserActivity:userActivity + restorationHandler:restorationHandler]; +}

And then on your React component you'll be able to listen to the events on +Linking as follows

componentDidMount() { + Linking.addEventListener('url', this._handleOpenURL); +}, +componentWillUnmount() { + Linking.removeEventListener('url', this._handleOpenURL); +}, +_handleOpenURL(event) { + console.log(event.url); +}

Opening external links #

To start the corresponding activity for a link (web URL, email, contact etc.), call

Linking.openURL(url).catch(err => console.error('An error occurred', err));

If you want to check if any installed app can handle a given URL beforehand you can call

Linking.canOpenURL(url).then(supported => { + if (!supported) { + console.log('Can\'t handle url: ' + url); + } else { + return Linking.openURL(url); + } +}).catch(err => console.error('An error occurred', err));

Methods #

constructor(0) #

addEventListener(type, handler) #

Add a handler to Linking changes by listening to the url event type +and providing the handler

removeEventListener(type, handler) #

Remove a handler by passing the url event type and the handler

openURL(url) #

Try to open the given url with any of the installed apps.

You can use other URLs, like a location (e.g. "geo:37.484847,-122.148386"), a contact, +or any other URL that can be opened with the installed apps.

NOTE: This method will fail if the system doesn't know how to open the specified URL. +If you're passing in a non-http(s) URL, it's best to check {@code canOpenURL} first.

NOTE: For web URLs, the protocol ("http://", "https://") must be set accordingly!

canOpenURL(url) #

Determine whether or not an installed app can handle a given URL.

NOTE: For web URLs, the protocol ("http://", "https://") must be set accordingly!

NOTE: As of iOS 9, your app needs to provide the LSApplicationQueriesSchemes key +inside Info.plist or canOpenURL will always return false.

@param URL the URL to open

getInitialURL(0) #

If the app launch was triggered by an app link, +it will give the link url, otherwise it will give null

NOTE: To support deep linking on Android, refer http://developer.android.com/training/app-indexing/deep-linking.html#handling-intents

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + Linking, + StyleSheet, + Text, + TouchableOpacity, + View, +} = ReactNative; +var UIExplorerBlock = require('./UIExplorerBlock'); + +class OpenURLButton extends React.Component { + static propTypes = { + url: React.PropTypes.string, + }; + + handleClick = () => { + Linking.canOpenURL(this.props.url).then(supported => { + if (supported) { + Linking.openURL(this.props.url); + } else { + console.log('Don\'t know how to open URI: ' + this.props.url); + } + }); + }; + + render() { + return ( + <TouchableOpacity + onPress={this.handleClick}> + <View style={styles.button}> + <Text style={styles.text}>Open {this.props.url}</Text> + </View> + </TouchableOpacity> + ); + } +} + +class IntentAndroidExample extends React.Component { + static title = 'Linking'; + static description = 'Shows how to use Linking to open URLs.'; + + render() { + return ( + <UIExplorerBlock title="Open external URLs"> + <OpenURLButton url={'https://www.facebook.com'} /> + <OpenURLButton url={'http://www.facebook.com'} /> + <OpenURLButton url={'http://facebook.com'} /> + <OpenURLButton url={'fb://notifications'} /> + <OpenURLButton url={'geo:37.484847,-122.148386'} /> + <OpenURLButton url={'tel:9876543210'} /> + </UIExplorerBlock> + ); + } +} + +var styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: 'white', + padding: 10, + paddingTop: 30, + }, + button: { + padding: 10, + backgroundColor: '#3B5998', + marginBottom: 10, + }, + text: { + color: 'white', + }, +}); + +module.exports = IntentAndroidExample;
\ No newline at end of file diff --git a/releases/0.37/docs/listview.html b/releases/0.37/docs/listview.html new file mode 100644 index 00000000000..943d2038016 --- /dev/null +++ b/releases/0.37/docs/listview.html @@ -0,0 +1,232 @@ +ListView

ListView #

ListView - A core component designed for efficient display of vertically +scrolling lists of changing data. The minimal API is to create a +ListView.DataSource, populate it with a simple +array of data blobs, and instantiate a ListView component with that data +source and a renderRow callback which takes a blob from the data array and +returns a renderable component.

Minimal example:

class MyComponent extends Component { + constructor() { + super(); + const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2}); + this.state = { + dataSource: ds.cloneWithRows(['row 1', 'row 2']), + }; + } + + render() { + return ( + <ListView + dataSource={this.state.dataSource} + renderRow={(rowData) => <Text>{rowData}</Text>} + /> + ); + } +}

ListView also supports more advanced features, including sections with sticky +section headers, header and footer support, callbacks on reaching the end of +the available data (onEndReached) and on the set of rows that are visible +in the device viewport change (onChangeVisibleRows), and several +performance optimizations.

There are a few performance operations designed to make ListView scroll +smoothly while dynamically loading potentially very large (or conceptually +infinite) data sets:

  • Only re-render changed rows - the rowHasChanged function provided to the +data source tells the ListView if it needs to re-render a row because the +source data has changed - see ListViewDataSource for more details.

  • Rate-limited row rendering - By default, only one row is rendered per +event-loop (customizable with the pageSize prop). This breaks up the +work into smaller chunks to reduce the chance of dropping frames while +rendering rows.

Props #

dataSource ListViewDataSource #

An instance of ListView.DataSource to use

enableEmptySections bool #

Flag indicating whether empty section headers should be rendered. In the future release +empty section headers will be rendered by default, and the flag will be deprecated. +If empty sections are not desired to be rendered their indices should be excluded from sectionID object.

initialListSize number #

How many rows to render on initial component mount. Use this to make +it so that the first screen worth of data appears at one time instead of +over the course of multiple frames.

onChangeVisibleRows function #

(visibleRows, changedRows) => void

Called when the set of visible rows changes. visibleRows maps +{ sectionID: { rowID: true }} for all the visible rows, and +changedRows maps { sectionID: { rowID: true | false }} for the rows +that have changed their visibility, with true indicating visible, and +false indicating the view has moved out of view.

onEndReached function #

Called when all rows have been rendered and the list has been scrolled +to within onEndReachedThreshold of the bottom. The native scroll +event is provided.

onEndReachedThreshold number #

Threshold in pixels (virtual, not physical) for calling onEndReached.

pageSize number #

Number of rows to render per event loop. Note: if your 'rows' are actually +cells, i.e. they don't span the full width of your view (as in the +ListViewGridLayoutExample), you should set the pageSize to be a multiple +of the number of cells per row, otherwise you're likely to see gaps at +the edge of the ListView as new pages are loaded.

removeClippedSubviews bool #

A performance optimization for improving scroll perf of +large lists, used in conjunction with overflow: 'hidden' on the row +containers. This is enabled by default.

renderFooter function #

() => renderable

The header and footer are always rendered (if these props are provided) +on every render pass. If they are expensive to re-render, wrap them +in StaticContainer or other mechanism as appropriate. Footer is always +at the bottom of the list, and header at the top, on every render pass.

renderHeader function #

renderRow function #

(rowData, sectionID, rowID, highlightRow) => renderable

Takes a data entry from the data source and its ids and should return +a renderable component to be rendered as the row. By default the data +is exactly what was put into the data source, but it's also possible to +provide custom extractors. ListView can be notified when a row is +being highlighted by calling highlightRow(sectionID, rowID). This +sets a boolean value of adjacentRowHighlighted in renderSeparator, allowing you +to control the separators above and below the highlighted row. The highlighted +state of a row can be reset by calling highlightRow(null).

renderScrollComponent function #

(props) => renderable

A function that returns the scrollable component in which the list rows +are rendered. Defaults to returning a ScrollView with the given props.

renderSectionHeader function #

(sectionData, sectionID) => renderable

If provided, a sticky header is rendered for this section. The sticky +behavior means that it will scroll with the content at the top of the +section until it reaches the top of the screen, at which point it will +stick to the top until it is pushed off the screen by the next section +header.

renderSeparator function #

(sectionID, rowID, adjacentRowHighlighted) => renderable

If provided, a renderable component to be rendered as the separator +below each row but not the last row if there is a section header below. +Take a sectionID and rowID of the row above and whether its adjacent row +is highlighted.

scrollRenderAheadDistance number #

How early to start rendering rows before they come on screen, in +pixels.

iosstickyHeaderIndices [number] #

An array of child indices determining which children get docked to the +top of the screen when scrolling. For example, passing +stickyHeaderIndices={[0]} will cause the first child to be fixed to the +top of the scroll view. This property is not supported in conjunction +with horizontal={true}.

Methods #

getMetrics(0) #

Exports some data, e.g. for perf investigations or analytics.

scrollTo(...args) #

Scrolls to a given x, y offset, either immediately or with a smooth animation.

See ScrollView#scrollTo.

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + Image, + ListView, + TouchableHighlight, + StyleSheet, + RecyclerViewBackedScrollView, + Text, + View, +} = ReactNative; + +var UIExplorerPage = require('./UIExplorerPage'); + +var ListViewSimpleExample = React.createClass({ + statics: { + title: '<ListView>', + description: 'Performant, scrollable list of data.' + }, + + getInitialState: function() { + var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2}); + return { + dataSource: ds.cloneWithRows(this._genRows({})), + }; + }, + + _pressData: ({}: {[key: number]: boolean}), + + componentWillMount: function() { + this._pressData = {}; + }, + + render: function() { + return ( + <UIExplorerPage + title={this.props.navigator ? null : '<ListView>'} + noSpacer={true} + noScroll={true}> + <ListView + dataSource={this.state.dataSource} + renderRow={this._renderRow} + renderScrollComponent={props => <RecyclerViewBackedScrollView {...props} />} + renderSeparator={this._renderSeparator} + /> + </UIExplorerPage> + ); + }, + + _renderRow: function(rowData: string, sectionID: number, rowID: number, highlightRow: (sectionID: number, rowID: number) => void) { + var rowHash = Math.abs(hashCode(rowData)); + var imgSource = THUMB_URLS[rowHash % THUMB_URLS.length]; + return ( + <TouchableHighlight onPress={() => { + this._pressRow(rowID); + highlightRow(sectionID, rowID); + }}> + <View> + <View style={styles.row}> + <Image style={styles.thumb} source={imgSource} /> + <Text style={styles.text}> + {rowData + ' - ' + LOREM_IPSUM.substr(0, rowHash % 301 + 10)} + </Text> + </View> + </View> + </TouchableHighlight> + ); + }, + + _genRows: function(pressData: {[key: number]: boolean}): Array<string> { + var dataBlob = []; + for (var ii = 0; ii < 100; ii++) { + var pressedText = pressData[ii] ? ' (pressed)' : ''; + dataBlob.push('Row ' + ii + pressedText); + } + return dataBlob; + }, + + _pressRow: function(rowID: number) { + this._pressData[rowID] = !this._pressData[rowID]; + this.setState({dataSource: this.state.dataSource.cloneWithRows( + this._genRows(this._pressData) + )}); + }, + + _renderSeparator: function(sectionID: number, rowID: number, adjacentRowHighlighted: bool) { + return ( + <View + key={`${sectionID}-${rowID}`} + style={{ + height: adjacentRowHighlighted ? 4 : 1, + backgroundColor: adjacentRowHighlighted ? '#3B5998' : '#CCCCCC', + }} + /> + ); + } +}); + +var THUMB_URLS = [ + require('./Thumbnails/like.png'), + require('./Thumbnails/dislike.png'), + require('./Thumbnails/call.png'), + require('./Thumbnails/fist.png'), + require('./Thumbnails/bandaged.png'), + require('./Thumbnails/flowers.png'), + require('./Thumbnails/heart.png'), + require('./Thumbnails/liking.png'), + require('./Thumbnails/party.png'), + require('./Thumbnails/poke.png'), + require('./Thumbnails/superlike.png'), + require('./Thumbnails/victory.png'), + ]; +var LOREM_IPSUM = 'Lorem ipsum dolor sit amet, ius ad pertinax oportere accommodare, an vix civibus corrumpit referrentur. Te nam case ludus inciderint, te mea facilisi adipiscing. Sea id integre luptatum. In tota sale consequuntur nec. Erat ocurreret mei ei. Eu paulo sapientem vulputate est, vel an accusam intellegam interesset. Nam eu stet pericula reprimique, ea vim illud modus, putant invidunt reprehendunt ne qui.'; + +/* eslint no-bitwise: 0 */ +var hashCode = function(str) { + var hash = 15; + for (var ii = str.length - 1; ii >= 0; ii--) { + hash = ((hash << 5) - hash) + str.charCodeAt(ii); + } + return hash; +}; + +var styles = StyleSheet.create({ + row: { + flexDirection: 'row', + justifyContent: 'center', + padding: 10, + backgroundColor: '#F6F6F6', + }, + thumb: { + width: 64, + height: 64, + }, + text: { + flex: 1, + }, +}); + +module.exports = ListViewSimpleExample;
\ No newline at end of file diff --git a/releases/0.37/docs/listviewdatasource.html b/releases/0.37/docs/listviewdatasource.html new file mode 100644 index 00000000000..c4c6cbe851e --- /dev/null +++ b/releases/0.37/docs/listviewdatasource.html @@ -0,0 +1,59 @@ +ListViewDataSource

ListViewDataSource #

Provides efficient data processing and access to the +ListView component. A ListViewDataSource is created with functions for +extracting data from the input blob, and comparing elements (with default +implementations for convenience). The input blob can be as simple as an +array of strings, or an object with rows nested inside section objects.

To update the data in the datasource, use cloneWithRows (or +cloneWithRowsAndSections if you care about sections). The data in the +data source is immutable, so you can't modify it directly. The clone methods +suck in the new data and compute a diff for each row so ListView knows +whether to re-render it or not.

In this example, a component receives data in chunks, handled by +_onDataArrived, which concats the new data onto the old data and updates the +data source. We use concat to create a new array - mutating this._data, +e.g. with this._data.push(newRowData), would be an error. _rowHasChanged +understands the shape of the row data and knows how to efficiently compare +it.

getInitialState: function() { + var ds = new ListViewDataSource({rowHasChanged: this._rowHasChanged}); + return {ds}; +}, +_onDataArrived(newData) { + this._data = this._data.concat(newData); + this.setState({ + ds: this.state.ds.cloneWithRows(this._data) + }); +}

Methods #

constructor(params) #

You can provide custom extraction and hasChanged functions for section +headers and rows. If absent, data will be extracted with the +defaultGetRowData and defaultGetSectionHeaderData functions.

The default extractor expects data of one of the following forms:

{ sectionID_1: { rowID_1: <rowData1>, ... }, ... }

or

{ sectionID_1: [ <rowData1>, <rowData2>, ... ], ... }

or

[ [ <rowData1>, <rowData2>, ... ], ... ]

The constructor takes in a params argument that can contain any of the +following:

  • getRowData(dataBlob, sectionID, rowID);
  • getSectionHeaderData(dataBlob, sectionID);
  • rowHasChanged(prevRowData, nextRowData);
  • sectionHeaderHasChanged(prevSectionData, nextSectionData);

cloneWithRows(dataBlob, rowIdentities) #

Clones this ListViewDataSource with the specified dataBlob and +rowIdentities. The dataBlob is just an arbitrary blob of data. At +construction an extractor to get the interesting information was defined +(or the default was used).

The rowIdentities is a 2D array of identifiers for rows. +ie. [['a1', 'a2'], ['b1', 'b2', 'b3'], ...]. If not provided, it's +assumed that the keys of the section data are the row identities.

Note: This function does NOT clone the data in this data source. It simply +passes the functions defined at construction to a new data source with +the data specified. If you wish to maintain the existing data you must +handle merging of old and new data separately and then pass that into +this function as the dataBlob.

cloneWithRowsAndSections(dataBlob, sectionIdentities, rowIdentities) #

This performs the same function as the cloneWithRows function but here +you also specify what your sectionIdentities are. If you don't care +about sections you should safely be able to use cloneWithRows.

sectionIdentities is an array of identifiers for sections. +ie. ['s1', 's2', ...]. If not provided, it's assumed that the +keys of dataBlob are the section identities.

Note: this returns a new object!

getRowCount(0) #

getRowAndSectionCount(0) #

rowShouldUpdate(sectionIndex, rowIndex) #

Returns if the row is dirtied and needs to be rerendered

getRowData(sectionIndex, rowIndex) #

Gets the data required to render the row.

getRowIDForFlatIndex(index) #

Gets the rowID at index provided if the dataSource arrays were flattened, +or null of out of range indexes.

getSectionIDForFlatIndex(index) #

Gets the sectionID at index provided if the dataSource arrays were flattened, +or null for out of range indexes.

getSectionLengths(0) #

Returns an array containing the number of rows in each section

sectionHeaderShouldUpdate(sectionIndex) #

Returns if the section header is dirtied and needs to be rerendered

getSectionHeaderData(sectionIndex) #

Gets the data required to render the section header

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/mapview.html b/releases/0.37/docs/mapview.html new file mode 100644 index 00000000000..39a67f6a26e --- /dev/null +++ b/releases/0.37/docs/mapview.html @@ -0,0 +1,488 @@ +MapView

MapView #

This component is only supported on iOS.

MapView is used to display embeddable maps and annotations using +MKMapView.

For a cross-platform solution, check out +react-native-maps +by Airbnb.

import React, { Component } from 'react'; +import { MapView } from 'react-native'; + +class MapMyRide extends Component { + render() { + return ( + <MapView + style={{height: 200, margin: 40}} + showsUserLocation={true} + /> + ); + } +}

Props #

annotations [{latitude: number, longitude: number, animateDrop: bool, draggable: bool, onDragStateChange: function, onFocus: function, onBlur: function, title: string, subtitle: string, leftCalloutView: element, rightCalloutView: element, detailCalloutView: element, tintColor: [object Object], image: Image.propTypes.source, view: element, id: string, hasLeftCallout: deprecatedPropType( + React.PropTypes.bool, + 'Use `leftCalloutView` instead.' +), hasRightCallout: deprecatedPropType( + React.PropTypes.bool, + 'Use `rightCalloutView` instead.' +), onLeftCalloutPress: deprecatedPropType( + React.PropTypes.func, + 'Use `leftCalloutView` instead.' +), onRightCalloutPress: deprecatedPropType( + React.PropTypes.func, + 'Use `rightCalloutView` instead.' +)}] #

Map annotations with title and subtitle.

followUserLocation bool #

If true the map will follow the user's location whenever it changes. +Note that this has no effect unless showsUserLocation is enabled. +Default value is true.

legalLabelInsets {top: number, left: number, bottom: number, right: number} #

Insets for the map's legal label, originally at bottom left of the map.

mapType enum('standard', 'satellite', 'hybrid') #

The map type to be displayed.

  • standard: Standard road map (default).
  • satellite: Satellite view.
  • hybrid: Satellite view with roads and points of interest overlaid.

maxDelta number #

Maximum size of the area that can be displayed.

minDelta number #

Minimum size of the area that can be displayed.

onAnnotationPress function #

Deprecated. Use annotation onFocus and onBlur instead.

onRegionChange function #

Callback that is called continuously when the user is dragging the map.

onRegionChangeComplete function #

Callback that is called once, when the user is done moving the map.

overlays [{coordinates: [object Object], lineWidth: number, strokeColor: [object Object], fillColor: [object Object], id: string}] #

Map overlays

pitchEnabled bool #

When this property is set to true and a valid camera is associated +with the map, the camera's pitch angle is used to tilt the plane +of the map.

When this property is set to false, the camera's pitch +angle is ignored and the map is always displayed as if the user +is looking straight down onto it.

region {latitude: number, longitude: number, latitudeDelta: number, longitudeDelta: number} #

The region to be displayed by the map.

The region is defined by the center coordinates and the span of +coordinates to display.

rotateEnabled bool #

When this property is set to true and a valid camera is associated with +the map, the camera's heading angle is used to rotate the plane of the +map around its center point.

When this property is set to false, the +camera's heading angle is ignored and the map is always oriented so +that true north is situated at the top of the map view

scrollEnabled bool #

If false the user won't be able to change the map region being displayed. +Default value is true.

showsCompass bool #

If false, compass won't be displayed on the map. +Default value is true.

showsPointsOfInterest bool #

If false points of interest won't be displayed on the map. +Default value is true.

showsUserLocation bool #

If true the app will ask for the user's location and display it on +the map. Default value is false.

NOTE: You'll need to add the NSLocationWhenInUseUsageDescription +key in Info.plist to enable geolocation, otherwise it will fail silently.

style View#style #

Used to style and layout the MapView.

zoomEnabled bool #

If false the user won't be able to pinch/zoom the map. +Default value is true.

androidactive bool #

Type Definitions #

AnnotationDragState #

State an annotation on the map.

Type:
$Enum

Constants:
ValueDescription
idle

Annotation is not being touched.

starting

Annotation dragging has began.

dragging

Annotation is being dragged.

canceling

Annotation dragging is being canceled.

ending

Annotation dragging has ended.

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { PropTypes } = React; +var { + Image, + MapView, + StyleSheet, + Text, + TextInput, + TouchableOpacity, + View, +} = ReactNative; + +var regionText = { + latitude: '0', + longitude: '0', + latitudeDelta: '0', + longitudeDelta: '0', +}; + +var MapRegionInput = React.createClass({ + + propTypes: { + region: PropTypes.shape({ + latitude: PropTypes.number.isRequired, + longitude: PropTypes.number.isRequired, + latitudeDelta: PropTypes.number, + longitudeDelta: PropTypes.number, + }), + onChange: PropTypes.func.isRequired, + }, + + getInitialState() { + return { + region: { + latitude: 0, + longitude: 0, + } + }; + }, + + componentWillReceiveProps: function(nextProps) { + this.setState({ + region: nextProps.region || this.getInitialState().region + }); + }, + + render: function() { + var region = this.state.region || this.getInitialState().region; + return ( + <View> + <View style={styles.row}> + <Text> + {'Latitude'} + </Text> + <TextInput + value={'' + region.latitude} + style={styles.textInput} + onChange={this._onChangeLatitude} + selectTextOnFocus={true} + /> + </View> + <View style={styles.row}> + <Text> + {'Longitude'} + </Text> + <TextInput + value={'' + region.longitude} + style={styles.textInput} + onChange={this._onChangeLongitude} + selectTextOnFocus={true} + /> + </View> + <View style={styles.row}> + <Text> + {'Latitude delta'} + </Text> + <TextInput + value={ + region.latitudeDelta == null ? '' : String(region.latitudeDelta) + } + style={styles.textInput} + onChange={this._onChangeLatitudeDelta} + selectTextOnFocus={true} + /> + </View> + <View style={styles.row}> + <Text> + {'Longitude delta'} + </Text> + <TextInput + value={ + region.longitudeDelta == null ? '' : String(region.longitudeDelta) + } + style={styles.textInput} + onChange={this._onChangeLongitudeDelta} + selectTextOnFocus={true} + /> + </View> + <View style={styles.changeButton}> + <Text onPress={this._change}> + {'Change'} + </Text> + </View> + </View> + ); + }, + + _onChangeLatitude: function(e) { + regionText.latitude = e.nativeEvent.text; + }, + + _onChangeLongitude: function(e) { + regionText.longitude = e.nativeEvent.text; + }, + + _onChangeLatitudeDelta: function(e) { + regionText.latitudeDelta = e.nativeEvent.text; + }, + + _onChangeLongitudeDelta: function(e) { + regionText.longitudeDelta = e.nativeEvent.text; + }, + + _change: function() { + this.setState({ + region: { + latitude: parseFloat(regionText.latitude), + longitude: parseFloat(regionText.longitude), + latitudeDelta: parseFloat(regionText.latitudeDelta), + longitudeDelta: parseFloat(regionText.longitudeDelta), + }, + }); + this.props.onChange(this.state.region); + }, + +}); + +class MapViewExample extends React.Component { + state = { + isFirstLoad: true, + mapRegion: undefined, + mapRegionInput: undefined, + annotations: [], + }; + + render() { + return ( + <View> + <MapView + style={styles.map} + onRegionChange={this._onRegionChange} + onRegionChangeComplete={this._onRegionChangeComplete} + region={this.state.mapRegion} + annotations={this.state.annotations} + /> + <MapRegionInput + onChange={this._onRegionInputChanged} + region={this.state.mapRegionInput} + /> + </View> + ); + } + + _getAnnotations = (region) => { + return [{ + longitude: region.longitude, + latitude: region.latitude, + title: 'You Are Here', + }]; + }; + + _onRegionChange = (region) => { + this.setState({ + mapRegionInput: region, + }); + }; + + _onRegionChangeComplete = (region) => { + if (this.state.isFirstLoad) { + this.setState({ + mapRegionInput: region, + annotations: this._getAnnotations(region), + isFirstLoad: false, + }); + } + }; + + _onRegionInputChanged = (region) => { + this.setState({ + mapRegion: region, + mapRegionInput: region, + annotations: this._getAnnotations(region), + }); + }; +} + +class AnnotationExample extends React.Component { + state = { + isFirstLoad: true, + annotations: [], + mapRegion: undefined, + }; + + render() { + if (this.state.isFirstLoad) { + var onRegionChangeComplete = (region) => { + this.setState({ + isFirstLoad: false, + annotations: [{ + longitude: region.longitude, + latitude: region.latitude, + ...this.props.annotation, + }], + }); + }; + } + + return ( + <MapView + style={styles.map} + onRegionChangeComplete={onRegionChangeComplete} + region={this.state.mapRegion} + annotations={this.state.annotations} + /> + ); + } +} + +class DraggableAnnotationExample extends React.Component { + state = { + isFirstLoad: true, + annotations: [], + mapRegion: undefined, + }; + + createAnnotation = (longitude, latitude) => { + return { + longitude, + latitude, + draggable: true, + onDragStateChange: (event) => { + if (event.state === 'idle') { + this.setState({ + annotations: [this.createAnnotation(event.longitude, event.latitude)], + }); + } + console.log('Drag state: ' + event.state); + }, + }; + }; + + render() { + if (this.state.isFirstLoad) { + var onRegionChangeComplete = (region) => { + //When the MapView loads for the first time, we can create the annotation at the + //region that was loaded. + this.setState({ + isFirstLoad: false, + annotations: [this.createAnnotation(region.longitude, region.latitude)], + }); + }; + } + + return ( + <MapView + style={styles.map} + onRegionChangeComplete={onRegionChangeComplete} + region={this.state.mapRegion} + annotations={this.state.annotations} + /> + ); + } +} + +var styles = StyleSheet.create({ + map: { + height: 150, + margin: 10, + borderWidth: 1, + borderColor: '#000000', + }, + row: { + flexDirection: 'row', + justifyContent: 'space-between', + }, + textInput: { + width: 150, + height: 20, + borderWidth: 0.5, + borderColor: '#aaaaaa', + fontSize: 13, + padding: 4, + }, + changeButton: { + alignSelf: 'center', + marginTop: 5, + padding: 3, + borderWidth: 0.5, + borderColor: '#777777', + }, +}); + +exports.displayName = (undefined: ?string); +exports.title = '<MapView>'; +exports.description = 'Base component to display maps'; +exports.examples = [ + { + title: 'Map', + render() { + return <MapViewExample />; + } + }, + { + title: 'showsUserLocation + followUserLocation', + render() { + return ( + <MapView + style={styles.map} + showsUserLocation={true} + followUserLocation={true} + /> + ); + } + }, + { + title: 'Callout example', + render() { + return <AnnotationExample style={styles.map} annotation={{ + title: 'More Info...', + rightCalloutView: ( + <TouchableOpacity + onPress={() => { + alert('You Are Here'); + }}> + <Image + style={{width:30, height:30}} + source={require('./uie_thumb_selected.png')} + /> + </TouchableOpacity> + ), + }}/>; + } + }, + { + title: 'Annotation focus example', + render() { + return <AnnotationExample style={styles.map} annotation={{ + title: 'More Info...', + onFocus: () => { + alert('Annotation gets focus'); + }, + onBlur: () => { + alert('Annotation lost focus'); + } + }}/>; + } + }, + { + title: 'Draggable pin', + render() { + return <DraggableAnnotationExample/>; + } + }, + { + title: 'Custom pin color', + render() { + return <AnnotationExample style={styles.map} annotation={{ + title: 'You Are Purple', + tintColor: MapView.PinColors.PURPLE, + }}/>; + } + }, + { + title: 'Custom pin image', + render() { + return <AnnotationExample style={styles.map} annotation={{ + title: 'Thumbs Up!', + image: require('./uie_thumb_big.png'), + }}/>; + } + }, + { + title: 'Custom pin view', + render() { + return <AnnotationExample style={styles.map} annotation={{ + title: 'Thumbs Up!', + view: <View style={{ + alignItems: 'center', + }}> + <Text style={{fontWeight: 'bold', color: '#f007'}}> + Thumbs Up! + </Text> + <Image + style={{width: 90, height: 65, resizeMode: 'cover'}} + source={require('./uie_thumb_big.png')} + /> + </View>, + }}/>; + } + }, + { + title: 'Custom overlay', + render() { + return <MapView + style={styles.map} + region={{ + latitude: 39.06, + longitude: -95.22, + }} + overlays={[{ + coordinates:[ + {latitude: 32.47, longitude: -107.85}, + {latitude: 45.13, longitude: -94.48}, + {latitude: 39.27, longitude: -83.25}, + {latitude: 32.47, longitude: -107.85}, + ], + strokeColor: '#f007', + lineWidth: 3, + }]} + />; + } + }, +];
\ No newline at end of file diff --git a/releases/0.37/docs/modal.html b/releases/0.37/docs/modal.html new file mode 100644 index 00000000000..8614aad12c6 --- /dev/null +++ b/releases/0.37/docs/modal.html @@ -0,0 +1,270 @@ +Modal

Modal #

The Modal component is a simple way to present content above an enclosing view.

Note: If you need more control over how to present modals over the rest of your app, +then consider using a top-level Navigator.

import React, { Component } from 'react'; +import { Modal, Text, TouchableHighlight, View } from 'react-native'; + +class ModalExample extends Component { + + state = { + modalVisible: false, + } + + setModalVisible(visible) { + this.setState({modalVisible: visible}); + } + + render() { + return ( + <View style={{marginTop: 22}}> + <Modal + animationType={"slide"} + transparent={false} + visible={this.state.modalVisible} + onRequestClose={() => {alert("Modal has been closed.")}} + > + <View style={{marginTop: 22}}> + <View> + <Text>Hello World!</Text> + + <TouchableHighlight onPress={() => { + this.setModalVisible(!this.state.modalVisible) + }}> + <Text>Hide Modal</Text> + </TouchableHighlight> + + </View> + </View> + </Modal> + + <TouchableHighlight onPress={() => { + this.setModalVisible(true) + }}> + <Text>Show Modal</Text> + </TouchableHighlight> + + </View> + ); + } +}

Props #

animated bool #

Deprecated

Use the animationType prop instead.

animationType enum('none', 'slide', 'fade') #

The animationType prop controls how the modal animates.

  • slide slides in from the bottom
  • fade fades into view
  • none appears without an animation

onRequestClose Platform.OS === 'android' ? PropTypes.func.isRequired : PropTypes.func #

The onRequestClose prop allows passing a function that will be called once the modal has been dismissed.

On the Android platform, this is a required function.

onShow function #

The onShow prop allows passing a function that will be called once the modal has been shown.

transparent bool #

The transparent prop determines whether your modal will fill the entire view. Setting this to true will render the modal over a transparent background.

visible bool #

The visible prop determines whether your modal is visible.

iosonOrientationChange function #

The onOrientationChange callback is called when the orientation changes while the modal is being displayed. +The orientation provided is only 'portrait' or 'landscape'. This callback is also called on initial render, regardless of the current orientation.

iossupportedOrientations [enum('portrait', 'portrait-upside-down', 'landscape', 'landscape-left', 'landscape-right')] #

The supportedOrientations prop allows the modal to be rotated to any of the specified orientations. +On iOS, the modal is still restricted by what's specified in your app's Info.plist's UISupportedInterfaceOrientations field.

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + Modal, + Picker, + StyleSheet, + Switch, + Text, + TouchableHighlight, + View, +} = ReactNative; + +const Item = Picker.Item; + +exports.displayName = (undefined: ?string); +exports.framework = 'React'; +exports.title = '<Modal>'; +exports.description = 'Component for presenting modal views.'; + +class Button extends React.Component { + state = { + active: false, + }; + + _onHighlight = () => { + this.setState({active: true}); + }; + + _onUnhighlight = () => { + this.setState({active: false}); + }; + + render() { + var colorStyle = { + color: this.state.active ? '#fff' : '#000', + }; + return ( + <TouchableHighlight + onHideUnderlay={this._onUnhighlight} + onPress={this.props.onPress} + onShowUnderlay={this._onHighlight} + style={[styles.button, this.props.style]} + underlayColor="#a9d9d4"> + <Text style={[styles.buttonText, colorStyle]}>{this.props.children}</Text> + </TouchableHighlight> + ); + } +} + +const supportedOrientationsPickerValues = [ + ['portrait'], + ['landscape'], + ['landscape-left'], + ['portrait', 'landscape-right'], + ['portrait', 'landscape'], + [], +]; + +class ModalExample extends React.Component { + state = { + animationType: 'none', + modalVisible: false, + transparent: false, + selectedSupportedOrientation: 0, + currentOrientation: 'unknown', + }; + + _setModalVisible = (visible) => { + this.setState({modalVisible: visible}); + }; + + _setAnimationType = (type) => { + this.setState({animationType: type}); + }; + + _toggleTransparent = () => { + this.setState({transparent: !this.state.transparent}); + }; + + render() { + var modalBackgroundStyle = { + backgroundColor: this.state.transparent ? 'rgba(0, 0, 0, 0.5)' : '#f5fcff', + }; + var innerContainerTransparentStyle = this.state.transparent + ? {backgroundColor: '#fff', padding: 20} + : null; + var activeButtonStyle = { + backgroundColor: '#ddd' + }; + + return ( + <View> + <Modal + animationType={this.state.animationType} + transparent={this.state.transparent} + visible={this.state.modalVisible} + onRequestClose={() => this._setModalVisible(false)} + supportedOrientations={supportedOrientationsPickerValues[this.state.selectedSupportedOrientation]} + onOrientationChange={evt => this.setState({currentOrientation: evt.nativeEvent.orientation})} + > + <View style={[styles.container, modalBackgroundStyle]}> + <View style={[styles.innerContainer, innerContainerTransparentStyle]}> + <Text>This modal was presented {this.state.animationType === 'none' ? 'without' : 'with'} animation.</Text> + <Text>It is currently displayed in {this.state.currentOrientation} mode.</Text> + <Button + onPress={this._setModalVisible.bind(this, false)} + style={styles.modalButton}> + Close + </Button> + </View> + </View> + </Modal> + <View style={styles.row}> + <Text style={styles.rowTitle}>Animation Type</Text> + <Button onPress={this._setAnimationType.bind(this, 'none')} style={this.state.animationType === 'none' ? activeButtonStyle : {}}> + none + </Button> + <Button onPress={this._setAnimationType.bind(this, 'slide')} style={this.state.animationType === 'slide' ? activeButtonStyle : {}}> + slide + </Button> + <Button onPress={this._setAnimationType.bind(this, 'fade')} style={this.state.animationType === 'fade' ? activeButtonStyle : {}}> + fade + </Button> + </View> + + <View style={styles.row}> + <Text style={styles.rowTitle}>Transparent</Text> + <Switch value={this.state.transparent} onValueChange={this._toggleTransparent} /> + </View> + + <View> + <Text style={styles.rowTitle}>Supported orientations</Text> + <Picker + selectedValue={this.state.selectedSupportedOrientation} + onValueChange={(_, i) => this.setState({selectedSupportedOrientation: i})} + itemStyle={styles.pickerItem} + > + <Item label="Portrait" value={0} /> + <Item label="Landscape" value={1} /> + <Item label="Landscape left" value={2} /> + <Item label="Portrait and landscape right" value={3} /> + <Item label="Portrait and landscape" value={4} /> + <Item label="Default supportedOrientations" value={5} /> + </Picker> + </View> + + <Button onPress={this._setModalVisible.bind(this, true)}> + Present + </Button> + </View> + ); + } +} + +exports.examples = [ + { + title: 'Modal Presentation', + description: 'Modals can be presented with or without animation', + render: () => <ModalExample />, + }, +]; + +var styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + padding: 20, + }, + innerContainer: { + borderRadius: 10, + alignItems: 'center', + }, + row: { + alignItems: 'center', + flex: 1, + flexDirection: 'row', + marginBottom: 20, + }, + rowTitle: { + flex: 1, + fontWeight: 'bold', + }, + button: { + borderRadius: 5, + flex: 1, + height: 44, + alignSelf: 'stretch', + justifyContent: 'center', + overflow: 'hidden', + }, + buttonText: { + fontSize: 18, + margin: 5, + textAlign: 'center', + }, + modalButton: { + marginTop: 10, + }, + pickerItem: { + fontSize: 16, + }, +});
\ No newline at end of file diff --git a/releases/0.37/docs/more-resources.html b/releases/0.37/docs/more-resources.html new file mode 100644 index 00000000000..3325417f3ff --- /dev/null +++ b/releases/0.37/docs/more-resources.html @@ -0,0 +1,19 @@ +More Resources

More Resources #

If you just read through this website, you should be able to build a pretty cool React Native app. But React Native isn't just a product made by one company - it's a community of thousands of developers. So if you're interested in React Native, here's some related stuff you might want to check out.

Popular Libraries #

If you're using React Native, you probably already know about React. So I feel a bit silly mentioning this. But if you haven't, check out React - it's the best way to build a modern website.

One common question is how to handle the "state" of your React Native application. The most popular library for this is Redux. Don't be afraid of how often Redux uses the word "reducer" - it's a pretty simple library, and there's also a nice series of videos explaining it.

If you're looking for a library that does a specific thing, check out Awesome React Native, a curated list of components that also has demos, articles, and other stuff.

Example Apps #

There are a lot of example apps at the React Native Playground. You can see the code running on a real device, which is a neat feature.

The folks who built the app for Facebook's F8 conference in 2016 also open-sourced the code and wrote up a detailed series of tutorials. This is useful if you want a more in-depth example that's more realistic than most sample apps out there.

Development Tools #

Nuclide is the IDE that Facebook uses internally for React Native development. The killer feature of Nuclide is its debugging ability. It also has great inline Flow support.

Ignite is a starter kit that uses Redux and a few different common UI libraries. It has a CLI to generate apps, components, and containers. If you like all of the individual tech choices, Ignite could be perfect for you.

CodePush is a service from Microsoft that makes it easy to deploy live updates to your React Native app. If you don't like going through the app store process to deploy little tweaks, and you also don't like setting up your own backend, give CodePush a try.

Exponent is a development environment plus application that focuses on letting you build React Native apps in the Exponent development environment, without ever touching Xcode or Android Studio. If you wish React Native was even more JavaScripty and webby, check out Exponent.

Deco is an all-in-one development environment specifically designed for React Native. It can automatically set up a new project, search for open source components, and insert them. You can also tweak your app graphically in real time. Check it out if you use macOS.

Where React Native People Hang Out #

The React Native Community Facebook group has thousands of developers, and it's pretty active. Come there to show off your project, or ask how other people solved similar problems.

Reactiflux is a Discord chat where a lot of React-related discussion happens, including React Native. Discord is just like Slack except it works better for open source projects with a zillion contributors. Check out the #react-native channel.

The React Twitter account covers both React and React Native. Follow the React Native Twitter account and blog to find out what's happening in the world of React Native.

There are a lot of React Native Meetups that happen around the world. Often there is React Native content in React meetups as well.

Sometimes we have React conferences. We posted the videos from React.js Conf 2016, and we'll probably have more conferences in the future, too. Stay tuned.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/native-components-android.html b/releases/0.37/docs/native-components-android.html new file mode 100644 index 00000000000..e70a8bfbbf7 --- /dev/null +++ b/releases/0.37/docs/native-components-android.html @@ -0,0 +1,103 @@ +Native UI Components

Native UI Components #

There are tons of native UI widgets out there ready to be used in the latest apps - some of them are part of the platform, others are available as third-party libraries, and still more might be in use in your very own portfolio. React Native has several of the most critical platform components already wrapped, like ScrollView and TextInput, but not all of them, and certainly not ones you might have written yourself for a previous app. Fortunately, it's quite easy to wrap up these existing components for seamless integration with your React Native application.

Like the native module guide, this too is a more advanced guide that assumes you are somewhat familiar with Android SDK programming. This guide will show you how to build a native UI component, walking you through the implementation of a subset of the existing ImageViewcomponent available in the core React Native library.

ImageView example #

For this example we are going to walk through the implementation requirements to allow the use of ImageViews in JavaScript.

Native views are created and manipulated by extending ViewManager or more commonly SimpleViewManager . A SimpleViewManager is convenient in this case because it applies common properties such as background color, opacity, and Flexbox layout.

These subclasses are essentially singletons - only one instance of each is created by the bridge. They vend native views to the NativeViewHierarchyManager, which delegates back to them to set and update the properties of the views as necessary. The ViewManagers are also typically the delegates for the views, sending events back to JavaScript via the bridge.

Vending a view is simple:

  1. Create the ViewManager subclass.
  2. Implement the createViewInstance method
  3. Expose view property setters using @ReactProp (or @ReactPropGroup) annotation
  4. Register the manager in createViewManagers of the applications package.
  5. Implement the JavaScript module

1. Create the ViewManager subclass #

In this example we create view manager class ReactImageManager that extends SimpleViewManager of type ReactImageView. ReactImageView is the type of object managed by the manager, this will be the custom native view. Name returned by getName is used to reference the native view type from JavaScript.

... + +public class ReactImageManager extends SimpleViewManager<ReactImageView> { + + public static final String REACT_CLASS = "RCTImageView"; + + @Override + public String getName() { + return REACT_CLASS; + }

2. Implement method createViewInstance #

Views are created in the createViewInstance method, the view should initialize itself in its default state, any properties will be set via a follow up call to updateView.

@Override + public ReactImageView createViewInstance(ThemedReactContext context) { + return new ReactImageView(context, Fresco.newDraweeControllerBuilder(), mCallerContext); + }

3. Expose view property setters using @ReactProp (or @ReactPropGroup) annotation #

Properties that are to be reflected in JavaScript needs to be exposed as setter method annotated with @ReactProp (or @ReactPropGroup). Setter method should take view to be updated (of the current view type) as a first argument and property value as a second argument. Setter should be declared as a void method and should be public. Property type sent to JS is determined automatically based on the type of value argument of the setter. The following type of values are currently supported: boolean, int, float, double, String, Boolean, Integer, ReadableArray, ReadableMap.

Annotation @ReactProp has one obligatory argument name of type String. Name assigned to the @ReactProp annotation linked to the setter method is used to reference the property on JS side.

Except from name, @ReactProp annotation may take following optional arguments: defaultBoolean, defaultInt, defaultFloat. Those arguments should be of the corresponding primitive type (accordingly boolean, int, float) and the value provided will be passed to the setter method in case when the property that the setter is referencing has been removed from the component. Note that "default" values are only provided for primitive types, in case when setter is of some complex type, null will be provided as a default value in case when corresponding property gets removed.

Setter declaration requirements for methods annotated with @ReactPropGroup are different than for @ReactProp, please refer to the @ReactPropGroup annotation class docs for more information about it.

IMPORTANT! in ReactJS updating the property value will result in setter method call. Note that one of the ways we can update component is by removing properties that has been set before. In that case setter method will be called as well to notify view manager that property has changed. In that case "default" value will be provided (for primitive types "default" can value can be specified using defaultBoolean, defaultFloat, etc. arguments of @ReactProp annotation, for complex types setter will be called with value set to null).

@ReactProp(name = "src") + public void setSrc(ReactImageView view, @Nullable String src) { + view.setSource(src); + } + + @ReactProp(name = "borderRadius", defaultFloat = 0f) + public void setBorderRadius(ReactImageView view, float borderRadius) { + view.setBorderRadius(borderRadius); + } + + @ReactProp(name = ViewProps.RESIZE_MODE) + public void setResizeMode(ReactImageView view, @Nullable String resizeMode) { + view.setScaleType(ImageResizeMode.toScaleType(resizeMode)); + }

4. Register the ViewManager #

The final Java step is to register the ViewManager to the application, this happens in a similar way to Native Modules, via the applications package member function createViewManagers.

@Override + public List<ViewManager> createViewManagers( + ReactApplicationContext reactContext) { + return Arrays.<ViewManager>asList( + new ReactImageManager() + ); + }

5. Implement the JavaScript module #

The very final step is to create the JavaScript module that defines the interface layer between Java and JavaScript for the users of your new view. Much of the effort is handled by internal React code in Java and JavaScript and all that is left for you is to describe the propTypes.

// ImageView.js + +import { PropTypes } from 'react'; +import { requireNativeComponent, View } from 'react-native'; + +var iface = { + name: 'ImageView', + propTypes: { + src: PropTypes.string, + borderRadius: PropTypes.number, + resizeMode: PropTypes.oneOf(['cover', 'contain', 'stretch']), + ...View.propTypes // include the default view properties + }, +}; + +module.exports = requireNativeComponent('RCTImageView', iface);

requireNativeComponent commonly takes two parameters, the first is the name of the native view and the second is an object that describes the component interface. The component interface should declare a friendly name for use in debug messages and must declare the propTypes reflected by the Native View. The propTypes are used for checking the validity of a user's use of the native view. Note that if you need your JavaScript component to do more than just specify a name and propTypes, like do custom event handling, you can wrap the native component in a normal react component. In that case, you want to pass in the wrapper component instead of iface to requireNativeComponent. This is illustrated in the MyCustomView example below.

Events #

So now we know how to expose native view components that we can control easily from JS, but how do we deal with events from the user, like pinch-zooms or panning? When a native event occurs the native code should issue an event to the JavaScript representation of the View, and the two views are linked with the value returned from the getId() method.

class MyCustomView extends View { + ... + public void onReceiveNativeEvent() { + WritableMap event = Arguments.createMap(); + event.putString("message", "MyMessage"); + ReactContext reactContext = (ReactContext)getContext(); + reactContext.getJSModule(RCTEventEmitter.class).receiveEvent( + getId(), + "topChange", + event); + } +}

The event name topChange maps to the onChange callback prop in JavaScript (mappings are in UIManagerModuleConstants.java). This callback is invoked with the raw event, which we typically process in the wrapper component to make a simpler API:

// MyCustomView.js + +class MyCustomView extends React.Component { + constructor(props) { + super(props); + this._onChange = this._onChange.bind(this); + } + _onChange(event: Event) { + if (!this.props.onChangeMessage) { + return; + } + this.props.onChangeMessage(event.nativeEvent.message); + } + render() { + return <RCTMyCustomView {...this.props} onChange={this._onChange} />; + } +} +MyCustomView.propTypes = { + /** + * Callback that is called continuously when the user is dragging the map. + */ + onChangeMessage: React.PropTypes.func, + ... +}; + +var RCTMyCustomView = requireNativeComponent(`RCTMyCustomView`, MyCustomView, { + nativeOnly: {onChange: true} +});

Note the use of nativeOnly above. Sometimes you'll have some special properties that you need to expose for the native component, but don't actually want them as part of the API for the associated React component. For example, Switch has a custom onChange handler for the raw native event, and exposes an onValueChange handler property that is invoked with just the boolean value rather than the raw event (similar to onChangeMessage in the example above). Since you don't want these native only properties to be part of the API, you don't want to put them in propTypes, but if you don't you'll get an error. The solution is simply to call them out via the nativeOnly option.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/native-components-ios.html b/releases/0.37/docs/native-components-ios.html new file mode 100644 index 00000000000..afa9c0e5d58 --- /dev/null +++ b/releases/0.37/docs/native-components-ios.html @@ -0,0 +1,268 @@ +Native UI Components

Native UI Components #

There are tons of native UI widgets out there ready to be used in the latest apps - some of them are part of the platform, others are available as third-party libraries, and still more might be in use in your very own portfolio. React Native has several of the most critical platform components already wrapped, like ScrollView and TextInput, but not all of them, and certainly not ones you might have written yourself for a previous app. Fortunately, it's quite easy to wrap up these existing components for seamless integration with your React Native application.

Like the native module guide, this too is a more advanced guide that assumes you are somewhat familiar with iOS programming. This guide will show you how to build a native UI component, walking you through the implementation of a subset of the existing MapView component available in the core React Native library.

iOS MapView example #

Let's say we want to add an interactive Map to our app - might as well use MKMapView, we just need to make it usable from JavaScript.

Native views are created and manipulated by subclasses of RCTViewManager. These subclasses are similar in function to view controllers, but are essentially singletons - only one instance of each is created by the bridge. They vend native views to the RCTUIManager, which delegates back to them to set and update the properties of the views as necessary. The RCTViewManagers are also typically the delegates for the views, sending events back to JavaScript via the bridge.

Vending a view is simple:

  • Create the basic subclass.
  • Add the RCT_EXPORT_MODULE() marker macro.
  • Implement the -(UIView *)view method.
// RCTMapManager.m +#import <MapKit/MapKit.h> + +#import "RCTViewManager.h" + +@interface RCTMapManager : RCTViewManager +@end + +@implementation RCTMapManager + +RCT_EXPORT_MODULE() + +- (UIView *)view +{ + return [[MKMapView alloc] init]; +} + +@end

Note: Do not attempt to set the frame or backgroundColor properties on the UIView instance that you vend through the -view method. React Native will overwrite the values set by your custom class in order to match your JavaScript component's layout props. If you need this granularity of control it might be better to wrap the UIView instance you want to style in another UIView and return the wrapper UIView instead. See Issue 2948 for more context.

Then you just need a little bit of JavaScript to make this a usable React component:

// MapView.js + +import { requireNativeComponent } from 'react-native'; + +// requireNativeComponent automatically resolves this to "RCTMapManager" +module.exports = requireNativeComponent('RCTMap', null);

This is now a fully-functioning native map view component in JavaScript, complete with pinch-zoom and other native gesture support. We can't really control it from JavaScript yet, though :(

Properties #

The first thing we can do to make this component more usable is to bridge over some native properties. Let's say we want to be able to disable pitch control and specify the visible region. Disabling pitch is a simple boolean, so we add this one line:

// RCTMapManager.m +RCT_EXPORT_VIEW_PROPERTY(pitchEnabled, BOOL)

Note that we explicitly specify the type as BOOL - React Native uses RCTConvert under the hood to convert all sorts of different data types when talking over the bridge, and bad values will show convenient "RedBox" errors to let you know there is an issue ASAP. When things are straightforward like this, the whole implementation is taken care of for you by this macro.

Now to actually disable pitch, we set the property in JS:

// MyApp.js +<MapView pitchEnabled={false} />

This isn't very well documented though - in order to know what properties are available and what values they accept, the client of your new component needs to dig through the Objective-C code. To make this better, let's make a wrapper component and document the interface with React PropTypes:

// MapView.js +import React from 'react'; +import { requireNativeComponent } from 'react-native'; + +class MapView extends React.Component { + render() { + return <RCTMap {...this.props} />; + } +} + +MapView.propTypes = { + /** + * When this property is set to `true` and a valid camera is associated + * with the map, the camera’s pitch angle is used to tilt the plane + * of the map. When this property is set to `false`, the camera’s pitch + * angle is ignored and the map is always displayed as if the user + * is looking straight down onto it. + */ + pitchEnabled: React.PropTypes.bool, +}; + +var RCTMap = requireNativeComponent('RCTMap', MapView); + +module.exports = MapView;

Now we have a nicely documented wrapper component that is easy to work with. Note that we changed the second argument to requireNativeComponent from null to the new MapView wrapper component. This allows the infrastructure to verify that the propTypes match the native props to reduce the chances of mismatches between the ObjC and JS code.

Next, let's add the more complex region prop. We start by adding the native code:

// RCTMapManager.m +RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap) +{ + [view setRegion:json ? [RCTConvert MKCoordinateRegion:json] : defaultView.region animated:YES]; +}

Ok, this is more complicated than the simple BOOL case we had before. Now we have a MKCoordinateRegion type that needs a conversion function, and we have custom code so that the view will animate when we set the region from JS. Within the function body that we provide, json refers to the raw value that has been passed from JS. There is also a view variable which gives us access to the manager's view instance, and a defaultView that we use to reset the property back to the default value if JS sends us a null sentinel.

You could write any conversion function you want for your view - here is the implementation for MKCoordinateRegion via two categories on RCTConvert:

@implementation RCTConvert(CoreLocation) + +RCT_CONVERTER(CLLocationDegrees, CLLocationDegrees, doubleValue); +RCT_CONVERTER(CLLocationDistance, CLLocationDistance, doubleValue); + ++ (CLLocationCoordinate2D)CLLocationCoordinate2D:(id)json +{ + json = [self NSDictionary:json]; + return (CLLocationCoordinate2D){ + [self CLLocationDegrees:json[@"latitude"]], + [self CLLocationDegrees:json[@"longitude"]] + }; +} + +@end + +@implementation RCTConvert(MapKit) + ++ (MKCoordinateSpan)MKCoordinateSpan:(id)json +{ + json = [self NSDictionary:json]; + return (MKCoordinateSpan){ + [self CLLocationDegrees:json[@"latitudeDelta"]], + [self CLLocationDegrees:json[@"longitudeDelta"]] + }; +} + ++ (MKCoordinateRegion)MKCoordinateRegion:(id)json +{ + return (MKCoordinateRegion){ + [self CLLocationCoordinate2D:json], + [self MKCoordinateSpan:json] + }; +}

These conversion functions are designed to safely process any JSON that the JS might throw at them by displaying "RedBox" errors and returning standard initialization values when missing keys or other developer errors are encountered.

To finish up support for the region prop, we need to document it in propTypes (or we'll get an error that the native prop is undocumented), then we can set it just like any other prop:

// MapView.js + +MapView.propTypes = { + /** + * When this property is set to `true` and a valid camera is associated + * with the map, the camera’s pitch angle is used to tilt the plane + * of the map. When this property is set to `false`, the camera’s pitch + * angle is ignored and the map is always displayed as if the user + * is looking straight down onto it. + */ + pitchEnabled: React.PropTypes.bool, + + /** + * The region to be displayed by the map. + * + * The region is defined by the center coordinates and the span of + * coordinates to display. + */ + region: React.PropTypes.shape({ + /** + * Coordinates for the center of the map. + */ + latitude: React.PropTypes.number.isRequired, + longitude: React.PropTypes.number.isRequired, + + /** + * Distance between the minimum and the maximum latitude/longitude + * to be displayed. + */ + latitudeDelta: React.PropTypes.number.isRequired, + longitudeDelta: React.PropTypes.number.isRequired, + }), +}; + +// MyApp.js + + render() { + var region = { + latitude: 37.48, + longitude: -122.16, + latitudeDelta: 0.1, + longitudeDelta: 0.1, + }; + return <MapView region={region} />; + }

Here you can see that the shape of the region is explicit in the JS documentation - ideally we could codegen some of this stuff, but that's not happening yet.

Sometimes you'll have some special properties that you need to expose for the native component, but don't actually want them as part of the API for the associated React component. For example, Switch has a custom onChange handler for the raw native event, and exposes an onValueChange handler property that is invoked with just the boolean value rather than the raw event. Since you don't want these native only properties to be part of the API, you don't want to put them in propTypes, but if you don't you'll get an error. The solution is simply to call them out via the nativeOnly option, e.g.

var RCTSwitch = requireNativeComponent('RCTSwitch', Switch, { + nativeOnly: { onChange: true } +});

Events #

So now we have a native map component that we can control easily from JS, but how do we deal with events from the user, like pinch-zooms or panning to change the visible region? The key is to declare an event handler property on RCTMapManager, make it a delegate for all the views it vends, and forward events to JS by calling the event handler block from the native view. This looks like so (simplified from the full implementation):

// RCTMap.h + +#import <MapKit/MapKit.h> + +#import "RCTComponent.h" + +@interface RCTMap: MKMapView + +@property (nonatomic, copy) RCTBubblingEventBlock onChange; + +@end
// RCTMap.m + +#import "RCTMap.h" + +@implementation RCTMap + +@end
// RCTMapManager.m + +#import "RCTMapManager.h" + +#import <MapKit/MapKit.h> + +#import "RCTMap.h" +#import "UIView+React.h" + +@interface RCTMapManager() <MKMapViewDelegate> +@end + +@implementation RCTMapManager + +RCT_EXPORT_MODULE() + +RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock) + +- (UIView *)view +{ + RCTMap *map = [RCTMap new]; + map.delegate = self; + return map; +} + +#pragma mark MKMapViewDelegate + +- (void)mapView:(RCTMap *)mapView regionDidChangeAnimated:(BOOL)animated +{ + if (!mapView.onChange) { + return; + } + + MKCoordinateRegion region = mapView.region; + mapView.onChange(@{ + @"region": @{ + @"latitude": @(region.center.latitude), + @"longitude": @(region.center.longitude), + @"latitudeDelta": @(region.span.latitudeDelta), + @"longitudeDelta": @(region.span.longitudeDelta), + } + }); +}

You can see we're adding an event handler property to the view by subclassing MKMapView. Then we're exposing the onChange event handler property and setting the manager as the delegate for every view that it vends. Finally, in the delegate method -mapView:regionDidChangeAnimated: the event handler block is called on the corresponding view with the region data. Calling the onChange event handler block results in calling the same callback prop in JavaScript. This callback is invoked with the raw event, which we typically process in the wrapper component to make a simpler API:

// MapView.js + +class MapView extends React.Component { + constructor() { + this._onChange = this._onChange.bind(this); + } + _onChange(event: Event) { + if (!this.props.onRegionChange) { + return; + } + this.props.onRegionChange(event.nativeEvent.region); + } + render() { + return <RCTMap {...this.props} onChange={this._onChange} />; + } +} +MapView.propTypes = { + /** + * Callback that is called continuously when the user is dragging the map. + */ + onRegionChange: React.PropTypes.func, + ... +};

Styles #

Since all our native react views are subclasses of UIView, most style attributes will work like you would expect out of the box. Some components will want a default style, however, for example UIDatePicker which is a fixed size. This default style is important for the layout algorithm to work as expected, but we also want to be able to override the default style when using the component. DatePickerIOS does this by wrapping the native component in an extra view, which has flexible styling, and using a fixed style (which is generated with constants passed in from native) on the inner native component:

// DatePickerIOS.ios.js + +import { UIManager } from 'react-native'; +var RCTDatePickerIOSConsts = UIManager.RCTDatePicker.Constants; +... + render: function() { + return ( + <View style={this.props.style}> + <RCTDatePickerIOS + ref={DATEPICKER} + style={styles.rkDatePickerIOS} + ... + /> + </View> + ); + } +}); + +var styles = StyleSheet.create({ + rkDatePickerIOS: { + height: RCTDatePickerIOSConsts.ComponentHeight, + width: RCTDatePickerIOSConsts.ComponentWidth, + }, +});

The RCTDatePickerIOSConsts constants are exported from native by grabbing the actual frame of the native component like so:

// RCTDatePickerManager.m + +- (NSDictionary *)constantsToExport +{ + UIDatePicker *dp = [[UIDatePicker alloc] init]; + [dp layoutIfNeeded]; + + return @{ + @"ComponentHeight": @(CGRectGetHeight(dp.frame)), + @"ComponentWidth": @(CGRectGetWidth(dp.frame)), + @"DatePickerModes": @{ + @"time": @(UIDatePickerModeTime), + @"date": @(UIDatePickerModeDate), + @"datetime": @(UIDatePickerModeDateAndTime), + } + }; +}

This guide covered many of the aspects of bridging over custom native components, but there is even more you might need to consider, such as custom hooks for inserting and laying out subviews. If you want to go even deeper, check out the actual RCTMapManager and other components in the source code.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/native-modules-android.html b/releases/0.37/docs/native-modules-android.html new file mode 100644 index 00000000000..8bc178656ee --- /dev/null +++ b/releases/0.37/docs/native-modules-android.html @@ -0,0 +1,290 @@ +Native Modules

Native Modules #

Sometimes an app needs access to a platform API that React Native doesn't have a corresponding module for yet. Maybe you want to reuse some existing Java code without having to reimplement it in JavaScript, or write some high performance, multi-threaded code such as for image processing, a database, or any number of advanced extensions.

We designed React Native such that it is possible for you to write real native code and have access to the full power of the platform. This is a more advanced feature and we don't expect it to be part of the usual development process, however it is essential that it exists. If React Native doesn't support a native feature that you need, you should be able to build it yourself.

Enable Gradle #

If you plan to make changes in Java code, we recommend enabling Gradle Daemon to speed up builds.

The Toast Module #

This guide will use the Toast example. Let's say we would like to be able to create a toast message from JavaScript.

We start by creating a native module. A native module is a Java class that usually extends the ReactContextBaseJavaModule class and implements the functionality required by the JavaScript. Our goal here is to be able to write ToastAndroid.show('Awesome', ToastAndroid.SHORT); from JavaScript to display a short toast on the screen.

package com.facebook.react.modules.toast; + +import android.widget.Toast; + +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; + +import java.util.Map; + +public class ToastModule extends ReactContextBaseJavaModule { + + private static final String DURATION_SHORT_KEY = "SHORT"; + private static final String DURATION_LONG_KEY = "LONG"; + + public ToastModule(ReactApplicationContext reactContext) { + super(reactContext); + } +}

ReactContextBaseJavaModule requires that a method called getName is implemented. The purpose of this method is to return the string name of the NativeModule which represents this class in JavaScript. So here we will call this ToastAndroid so that we can access it through React.NativeModules.ToastAndroid in JavaScript.

@Override + public String getName() { + return "ToastAndroid"; + }

An optional method called getConstants returns the constant values exposed to JavaScript. Its implementation is not required but is very useful to key pre-defined values that need to be communicated from JavaScript to Java in sync.

@Override + public Map<String, Object> getConstants() { + final Map<String, Object> constants = new HashMap<>(); + constants.put(DURATION_SHORT_KEY, Toast.LENGTH_SHORT); + constants.put(DURATION_LONG_KEY, Toast.LENGTH_LONG); + return constants; + }

To expose a method to JavaScript a Java method must be annotated using @ReactMethod. The return type of bridge methods is always void. React Native bridge is asynchronous, so the only way to pass a result to JavaScript is by using callbacks or emitting events (see below).

@ReactMethod + public void show(String message, int duration) { + Toast.makeText(getReactApplicationContext(), message, duration).show(); + }

Argument Types #

The following argument types are supported for methods annotated with @ReactMethod and they directly map to their JavaScript equivalents

Boolean -> Bool +Integer -> Number +Double -> Number +Float -> Number +String -> String +Callback -> function +ReadableMap -> Object +ReadableArray -> Array

Read more about ReadableMap and ReadableArray

Register the Module #

The last step within Java is to register the Module; this happens in the createNativeModules of your apps package. If a module is not registered it will not be available from JavaScript.

class AnExampleReactPackage implements ReactPackage { + + @Override + public List<Class<? extends JavaScriptModule>> createJSModules() { + return Collections.emptyList(); + } + + @Override + public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { + return Collections.emptyList(); + } + + @Override + public List<NativeModule> createNativeModules( + ReactApplicationContext reactContext) { + List<NativeModule> modules = new ArrayList<>(); + + modules.add(new ToastModule(reactContext)); + + return modules; + }

The package needs to be provided in the getPackages method of the MainApplication.java file. This file exists under the android folder in your react-native application directory. The path to this file is: android/app/src/main/java/com/your-app-name/MainApplication.java.

protected List<ReactPackage> getPackages() { + return Arrays.<ReactPackage>asList( + new MainReactPackage(), + new AnExampleReactPackage()); // <-- Add this line with your package name. +}

To make it simpler to access your new functionality from JavaScript, it is common to wrap the native module in a JavaScript module. This is not necessary but saves the consumers of your library the need to pull it off of NativeModules each time. This JavaScript file also becomes a good location for you to add any JavaScript side functionality.

'use strict'; +/** + * This exposes the native ToastAndroid module as a JS module. This has a + * function 'show' which takes the following parameters: + * + * 1. String message: A string with the text to toast + * 2. int duration: The duration of the toast. May be ToastAndroid.SHORT or + * ToastAndroid.LONG + */ +import { NativeModules } from 'react-native'; +module.exports = NativeModules.ToastAndroid;

Now, from your other JavaScript file you can call the method like this:

import ToastAndroid from './ToastAndroid'; + +ToastAndroid.show('Awesome', ToastAndroid.SHORT);

Beyond Toasts #

Callbacks #

Native modules also support a special kind of argument - a callback. In most cases it is used to provide the function call result to JavaScript.

public class UIManagerModule extends ReactContextBaseJavaModule { + +... + + @ReactMethod + public void measureLayout( + int tag, + int ancestorTag, + Callback errorCallback, + Callback successCallback) { + try { + measureLayout(tag, ancestorTag, mMeasureBuffer); + float relativeX = PixelUtil.toDIPFromPixel(mMeasureBuffer[0]); + float relativeY = PixelUtil.toDIPFromPixel(mMeasureBuffer[1]); + float width = PixelUtil.toDIPFromPixel(mMeasureBuffer[2]); + float height = PixelUtil.toDIPFromPixel(mMeasureBuffer[3]); + successCallback.invoke(relativeX, relativeY, width, height); + } catch (IllegalViewOperationException e) { + errorCallback.invoke(e.getMessage()); + } + } + +...

This method would be accessed in JavaScript using:

UIManager.measureLayout( + 100, + 100, + (msg) => { + console.log(msg); + }, + (x, y, width, height) => { + console.log(x + ':' + y + ':' + width + ':' + height); + } +);

A native module is supposed to invoke its callback only once. It can, however, store the callback and invoke it later.

It is very important to highlight that the callback is not invoked immediately after the native function completes - remember that bridge communication is asynchronous, and this too is tied to the run loop.

Promises #

Native modules can also fulfill a promise, which can simplify your code, especially when using ES2016's async/await syntax. When the last parameter of a bridged native method is a Promise, its corresponding JS method will return a JS Promise object.

Refactoring the above code to use a promise instead of callbacks looks like this:

public class UIManagerModule extends ReactContextBaseJavaModule { + +... + private static final String E_LAYOUT_ERROR = "E_LAYOUT_ERROR"; + @ReactMethod + public void measureLayout( + int tag, + int ancestorTag, + Promise promise) { + try { + measureLayout(tag, ancestorTag, mMeasureBuffer); + + WritableMap map = Arguments.createMap(); + + map.putDouble("relativeX", PixelUtil.toDIPFromPixel(mMeasureBuffer[0])); + map.putDouble("relativeY", PixelUtil.toDIPFromPixel(mMeasureBuffer[1])); + map.putDouble("width", PixelUtil.toDIPFromPixel(mMeasureBuffer[2])); + map.putDouble("height", PixelUtil.toDIPFromPixel(mMeasureBuffer[3])); + + promise.resolve(map); + } catch (IllegalViewOperationException e) { + promise.reject(E_LAYOUT_ERROR, e); + } + } + +...

The JavaScript counterpart of this method returns a Promise. This means you can use the await keyword within an async function to call it and wait for its result:

async function measureLayout() { + try { + var { + relativeX, + relativeY, + width, + height, + } = await UIManager.measureLayout(100, 100); + + console.log(relativeX + ':' + relativeY + ':' + width + ':' + height); + } catch (e) { + console.error(e); + } +} + +measureLayout();

Threading #

Native modules should not have any assumptions about what thread they are being called on, as the current assignment is subject to change in the future. If a blocking call is required, the heavy work should be dispatched to an internally managed worker thread, and any callbacks distributed from there.

Sending Events to JavaScript #

Native modules can signal events to JavaScript without being invoked directly. The easiest way to do this is to use the RCTDeviceEventEmitter which can be obtained from the ReactContext as in the code snippet below.

... +private void sendEvent(ReactContext reactContext, + String eventName, + @Nullable WritableMap params) { + reactContext + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit(eventName, params); +} +... +WritableMap params = Arguments.createMap(); +... +sendEvent(reactContext, "keyboardWillShow", params);

JavaScript modules can then register to receive events by addListenerOn using the Subscribable mixin

import { DeviceEventEmitter } from 'react-native'; +... + +var ScrollResponderMixin = { + mixins: [Subscribable.Mixin], + + + componentWillMount: function() { + ... + this.addListenerOn(DeviceEventEmitter, + 'keyboardWillShow', + this.scrollResponderKeyboardWillShow); + ... + }, + scrollResponderKeyboardWillShow:function(e: Event) { + this.keyboardWillOpenTo = e; + this.props.onKeyboardWillShow && this.props.onKeyboardWillShow(e); + },

You can also directly use the DeviceEventEmitter module to listen for events.

... +componentWillMount: function() { + DeviceEventEmitter.addListener('keyboardWillShow', function(e: Event) { + // handle event. + }); +} +...

Getting activity result from startActivityForResult #

You'll need to listen to onActivityResult if you want to get results from an activity you started with startActivityForResult. To do this, the you must extend BaseActivityEventListener or implement ActivityEventListener. The former is preferred as it is more resilient to API changes. Then, you need to register the listener in the module's constructor,

reactContext.addActivityEventListener(mActivityResultListener);

Now you can listen to onActivityResult by implementing the following method:

@Override +public void onActivityResult( + final Activity activity, + final int requestCode, + final int resultCode, + final Intent intent) { + // Your logic here +}

We will implement a simple image picker to demonstrate this. The image picker will expose the method pickImage to JavaScript, which will return the path of the image when called.

public class ImagePickerModule extends ReactContextBaseJavaModule { + + private static final int IMAGE_PICKER_REQUEST = 467081; + private static final String E_ACTIVITY_DOES_NOT_EXIST = "E_ACTIVITY_DOES_NOT_EXIST"; + private static final String E_PICKER_CANCELLED = "E_PICKER_CANCELLED"; + private static final String E_FAILED_TO_SHOW_PICKER = "E_FAILED_TO_SHOW_PICKER"; + private static final String E_NO_IMAGE_DATA_FOUND = "E_NO_IMAGE_DATA_FOUND"; + + private Promise mPickerPromise; + + private final ActivityEventListener mActivityEventListener = new BaseActivityEventListener() { + + @Override + public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) { + if (requestCode == IMAGE_PICKER_REQUEST) { + if (mPickerPromise != null) { + if (resultCode == Activity.RESULT_CANCELED) { + mPickerPromise.reject(E_PICKER_CANCELLED, "Image picker was cancelled"); + } else if (resultCode == Activity.RESULT_OK) { + Uri uri = intent.getData(); + + if (uri == null) { + mPickerPromise.reject(E_NO_IMAGE_DATA_FOUND, "No image data found"); + } else { + mPickerPromise.resolve(uri.toString()); + } + } + + mPickerPromise = null; + } + } + } + }; + + public ImagePickerModule(ReactApplicationContext reactContext) { + super(reactContext); + + // Add the listener for `onActivityResult` + reactContext.addActivityEventListener(mActivityEventListener); + } + + @Override + public String getName() { + return "ImagePickerModule"; + } + + @ReactMethod + public void pickImage(final Promise promise) { + Activity currentActivity = getCurrentActivity(); + + if (currentActivity == null) { + promise.reject(E_ACTIVITY_DOES_NOT_EXIST, "Activity doesn't exist"); + return; + } + + // Store the promise to resolve/reject when picker returns data + mPickerPromise = promise; + + try { + final Intent galleryIntent = new Intent(Intent.ACTION_PICK); + + galleryIntent.setType("image/*"); + + final Intent chooserIntent = Intent.createChooser(galleryIntent, "Pick an image"); + + currentActivity.startActivityForResult(chooserIntent, IMAGE_PICKER_REQUEST); + } catch (Exception e) { + mPickerPromise.reject(E_FAILED_TO_SHOW_PICKER, e); + mPickerPromise = null; + } + } +}

Listening to LifeCycle events #

Listening to the activity's LifeCycle events such as onResume, onPause etc. is very similar to how we implemented ActivityEventListener. The module must implement LifecycleEventListener. Then, you need to register a listener in the module's constructor,

reactContext.addLifecycleEventListener(this);

Now you can listen to the activity's LifeCycle events by implementing the following methods:

@Override +public void onHostResume() { + // Activity `onResume` +} + +@Override +public void onHostPause() { + // Activity `onPause` +} + +@Override +public void onHostDestroy() { + // Activity `onDestroy` +}

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/native-modules-ios.html b/releases/0.37/docs/native-modules-ios.html new file mode 100644 index 00000000000..b7bcdc6d674 --- /dev/null +++ b/releases/0.37/docs/native-modules-ios.html @@ -0,0 +1,172 @@ +Native Modules

Native Modules #

Sometimes an app needs access to platform API, and React Native doesn't have a corresponding module yet. Maybe you want to reuse some existing Objective-C, Swift or C++ code without having to reimplement it in JavaScript, or write some high performance, multi-threaded code such as for image processing, a database, or any number of advanced extensions.

We designed React Native such that it is possible for you to write real native code and have access to the full power of the platform. This is a more advanced feature and we don't expect it to be part of the usual development process, however it is essential that it exists. If React Native doesn't support a native feature that you need, you should be able to build it yourself.

This is a more advanced guide that shows how to build a native module. It assumes the reader knows Objective-C or Swift and core libraries (Foundation, UIKit).

iOS Calendar Module Example #

This guide will use the iOS Calendar API example. Let's say we would like to be able to access the iOS calendar from JavaScript.

A native module is just an Objective-C class that implements the RCTBridgeModule protocol. If you are wondering, RCT is an abbreviation of ReaCT.

// CalendarManager.h +#import "RCTBridgeModule.h" + +@interface CalendarManager : NSObject <RCTBridgeModule> +@end

In addition to implementing the RCTBridgeModule protocol, your class must also include the RCT_EXPORT_MODULE() macro. This takes an optional argument that specifies the name that the module will be accessible as in your JavaScript code (more on this later). If you do not specify a name, the JavaScript module name will match the Objective-C class name.

// CalendarManager.m +@implementation CalendarManager + +RCT_EXPORT_MODULE(); + +@end

React Native will not expose any methods of CalendarManager to JavaScript unless explicitly told to. This is done using the RCT_EXPORT_METHOD() macro:

#import "CalendarManager.h" +#import "RCTLog.h" + +@implementation CalendarManager + +RCT_EXPORT_MODULE(); + +RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location) +{ + RCTLogInfo(@"Pretending to create an event %@ at %@", name, location); +}

Now, from your JavaScript file you can call the method like this:

import { NativeModules } from 'react-native'; +var CalendarManager = NativeModules.CalendarManager; +CalendarManager.addEvent('Birthday Party', '4 Privet Drive, Surrey');

NOTE: JavaScript method names

The name of the method exported to JavaScript is the native method's name up to the first colon. React Native also defines a macro called RCT_REMAP_METHOD() to specify the JavaScript method's name. This is useful when multiple native methods are the same up to the first colon and would have conflicting JavaScript names.

The CalendarManager module is instantiated on the Objective-C side using a [CalendarManager new] call. The return type of bridge methods is always void. React Native bridge is asynchronous, so the only way to pass a result to JavaScript is by using callbacks or emitting events (see below).

Argument Types #

RCT_EXPORT_METHOD supports all standard JSON object types, such as:

  • string (NSString)
  • number (NSInteger, float, double, CGFloat, NSNumber)
  • boolean (BOOL, NSNumber)
  • array (NSArray) of any types from this list
  • object (NSDictionary) with string keys and values of any type from this list
  • function (RCTResponseSenderBlock)

But it also works with any type that is supported by the RCTConvert class (see RCTConvert for details). The RCTConvert helper functions all accept a JSON value as input and map it to a native Objective-C type or class.

In our CalendarManager example, we need to pass the event date to the native method. We can't send JavaScript Date objects over the bridge, so we need to convert the date to a string or number. We could write our native function like this:

RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location date:(nonnull NSNumber *)secondsSinceUnixEpoch) +{ + NSDate *date = [RCTConvert NSDate:secondsSinceUnixEpoch]; +}

or like this:

RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location date:(NSString *)ISO8601DateString) +{ + NSDate *date = [RCTConvert NSDate:ISO8601DateString]; +}

But by using the automatic type conversion feature, we can skip the manual conversion step completely, and just write:

RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location date:(NSDate *)date) +{ + // Date is ready to use! +}

You would then call this from JavaScript by using either:

CalendarManager.addEvent('Birthday Party', '4 Privet Drive, Surrey', date.getTime()); // passing date as number of seconds since Unix epoch

or

CalendarManager.addEvent('Birthday Party', '4 Privet Drive, Surrey', date.toISOString()); // passing date as ISO-8601 string

And both values would get converted correctly to the native NSDate. A bad value, like an Array, would generate a helpful "RedBox" error message.

As CalendarManager.addEvent method gets more and more complex, the number of arguments will grow. Some of them might be optional. In this case it's worth considering changing the API a little bit to accept a dictionary of event attributes, like this:

#import "RCTConvert.h" + +RCT_EXPORT_METHOD(addEvent:(NSString *)name details:(NSDictionary *)details) +{ + NSString *location = [RCTConvert NSString:details[@"location"]]; + NSDate *time = [RCTConvert NSDate:details[@"time"]]; + ... +}

and call it from JavaScript:

CalendarManager.addEvent('Birthday Party', { + location: '4 Privet Drive, Surrey', + time: date.getTime(), + description: '...' +})

NOTE: About array and map

Objective-C doesn't provide any guarantees about the types of values in these structures. Your native module might expect an array of strings, but if JavaScript calls your method with an array containing numbers and strings, you'll get an NSArray containing a mix of NSNumber and NSString. For arrays, RCTConvert provides some typed collections you can use in your method declaration, such as NSStringArray, or UIColorArray. For maps, it is the developer's responsibility to check the value types individually by manually calling RCTConvert helper methods.

Callbacks #

WARNING

This section is more experimental than others because we don't have a solid set of best practices around callbacks yet.

Native modules also supports a special kind of argument- a callback. In most cases it is used to provide the function call result to JavaScript.

RCT_EXPORT_METHOD(findEvents:(RCTResponseSenderBlock)callback) +{ + NSArray *events = ... + callback(@[[NSNull null], events]); +}

RCTResponseSenderBlock accepts only one argument - an array of parameters to pass to the JavaScript callback. In this case we use node's convention to make the first parameter an error object (usually null when there is no error) and the rest are the results of the function.

CalendarManager.findEvents((error, events) => { + if (error) { + console.error(error); + } else { + this.setState({events: events}); + } +})

A native module is supposed to invoke its callback only once. It can, however, store the callback and invoke it later. This pattern is often used to wrap iOS APIs that require delegates. See RCTAlertManager for an example.

If you want to pass error-like objects to JavaScript, use RCTMakeError from RCTUtils.h. Right now this just passes an Error-shaped dictionary to JavaScript, but we would like to automatically generate real JavaScript Error objects in the future.

Promises #

Native modules can also fulfill a promise, which can simplify your code, especially when using ES2016's async/await syntax. When the last parameters of a bridged native method are an RCTPromiseResolveBlock and RCTPromiseRejectBlock, its corresponding JS method will return a JS Promise object.

Refactoring the above code to use a promise instead of callbacks looks like this:

RCT_REMAP_METHOD(findEvents, + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) +{ + NSArray *events = ... + if (events) { + resolve(events); + } else { + NSError *error = ... + reject(@"no_events", @"There were no events", error); + } +}

The JavaScript counterpart of this method returns a Promise. This means you can use the await keyword within an async function to call it and wait for its result:

async function updateEvents() { + try { + var events = await CalendarManager.findEvents(); + + this.setState({ events }); + } catch (e) { + console.error(e); + } +} + +updateEvents();

Threading #

The native module should not have any assumptions about what thread it is being called on. React Native invokes native modules methods on a separate serial GCD queue, but this is an implementation detail and might change. The - (dispatch_queue_t)methodQueue method allows the native module to specify which queue its methods should be run on. For example, if it needs to use a main-thread-only iOS API, it should specify this via:

- (dispatch_queue_t)methodQueue +{ + return dispatch_get_main_queue(); +}

Similarly, if an operation may take a long time to complete, the native module should not block and can specify it's own queue to run operations on. For example, the RCTAsyncLocalStorage module creates it's own queue so the React queue isn't blocked waiting on potentially slow disk access:

- (dispatch_queue_t)methodQueue +{ + return dispatch_queue_create("com.facebook.React.AsyncLocalStorageQueue", DISPATCH_QUEUE_SERIAL); +}

The specified methodQueue will be shared by all of the methods in your module. If just one of your methods is long-running (or needs to be run on a different queue than the others for some reason), you can use dispatch_async inside the method to perform that particular method's code on another queue, without affecting the others:

RCT_EXPORT_METHOD(doSomethingExpensive:(NSString *)param callback:(RCTResponseSenderBlock)callback) +{ + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + // Call long-running code on background thread + ... + // You can invoke callback from any thread/queue + callback(@[...]); + }); +}

NOTE: Sharing dispatch queues between modules

The methodQueue method will be called once when the module is initialized, and then retained by the bridge, so there is no need to retain the queue yourself, unless you wish to make use of it within your module. However, if you wish to share the same queue between multiple modules then you will need to ensure that you retain and return the same queue instance for each of them; merely returning a queue of the same name for each won't work.

Dependency Injection #

The bridge initializes any registered RCTBridgeModules automatically, however you may wish to instantiate your own module instances (so you may inject dependencies, for example).

You can do this by creating a class that implements the RTCBridgeDelegate Protocol, initializing an RTCBridge with the delegate as an argument and initialising a RTCRootView with the initialized bridge.

id<RCTBridgeDelegate> moduleInitialiser = [[classThatImplementsRTCBridgeDelegate alloc] init]; + +RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:moduleInitialiser launchOptions:nil]; + +RCTRootView *rootView = [[RCTRootView alloc] + initWithBridge:bridge + moduleName:kModuleName + initialProperties:nil];

Exporting Constants #

A native module can export constants that are immediately available to JavaScript at runtime. This is useful for communicating static data that would otherwise require a round-trip through the bridge.

- (NSDictionary *)constantsToExport +{ + return @{ @"firstDayOfTheWeek": @"Monday" }; +}

JavaScript can use this value right away, synchronously:

console.log(CalendarManager.firstDayOfTheWeek);

Note that the constants are exported only at initialization time, so if you change constantsToExport values at runtime it won't affect the JavaScript environment.

Enum Constants #

Enums that are defined via NS_ENUM cannot be used as method arguments without first extending RCTConvert.

In order to export the following NS_ENUM definition:

typedef NS_ENUM(NSInteger, UIStatusBarAnimation) { + UIStatusBarAnimationNone, + UIStatusBarAnimationFade, + UIStatusBarAnimationSlide, +};

You must create a class extension of RCTConvert like so:

@implementation RCTConvert (StatusBarAnimation) + RCT_ENUM_CONVERTER(UIStatusBarAnimation, (@{ @"statusBarAnimationNone" : @(UIStatusBarAnimationNone), + @"statusBarAnimationFade" : @(UIStatusBarAnimationFade), + @"statusBarAnimationSlide" : @(UIStatusBarAnimationSlide)}), + UIStatusBarAnimationNone, integerValue) +@end

You can then define methods and export your enum constants like this:

- (NSDictionary *)constantsToExport +{ + return @{ @"statusBarAnimationNone" : @(UIStatusBarAnimationNone), + @"statusBarAnimationFade" : @(UIStatusBarAnimationFade), + @"statusBarAnimationSlide" : @(UIStatusBarAnimationSlide) } +}; + +RCT_EXPORT_METHOD(updateStatusBarAnimation:(UIStatusBarAnimation)animation + completion:(RCTResponseSenderBlock)callback)

Your enum will then be automatically unwrapped using the selector provided (integerValue in the above example) before being passed to your exported method.

Sending Events to JavaScript #

The native module can signal events to JavaScript without being invoked directly. The easiest way to do this is to use eventDispatcher:

#import "RCTBridge.h" +#import "RCTEventDispatcher.h" + +@implementation CalendarManager + +@synthesize bridge = _bridge; + +- (void)calendarEventReminderReceived:(NSNotification *)notification +{ + NSString *eventName = notification.userInfo[@"name"]; + [self.bridge.eventDispatcher sendAppEventWithName:@"EventReminder" + body:@{@"name": eventName}]; +} + +@end

JavaScript code can subscribe to these events:

import { NativeAppEventEmitter } from 'react-native'; + +var subscription = NativeAppEventEmitter.addListener( + 'EventReminder', + (reminder) => console.log(reminder.name) +); +... +// Don't forget to unsubscribe, typically in componentWillUnmount +subscription.remove();

For more examples of sending events to JavaScript, see RCTLocationObserver.

Exporting Swift #

Swift doesn't have support for macros so exposing it to React Native requires a bit more setup but works relatively the same.

Let's say we have the same CalendarManager but as a Swift class:

// CalendarManager.swift + +@objc(CalendarManager) +class CalendarManager: NSObject { + + @objc(addEvent:location:date:) + func addEvent(name: String, location: String, date: NSNumber) -> Void { + // Date is ready to use! + } + +}

NOTE: It is important to use the @objc modifiers to ensure the class and functions are exported properly to the Objective-C runtime.

Then create a private implementation file that will register the required information with the React Native bridge:

// CalendarManagerBridge.m +#import "RCTBridgeModule.h" + +@interface RCT_EXTERN_MODULE(CalendarManager, NSObject) + +RCT_EXTERN_METHOD(addEvent:(NSString *)name location:(NSString *)location date:(nonnull NSNumber *)date) + +@end

For those of you new to Swift and Objective-C, whenever you mix the two languages in an iOS project, you will also need an additional bridging file, known as a bridging header, to expose the Objective-C files to Swift. Xcode will offer to create this header file for you if you add your Swift file to your app through the Xcode File>New File menu option. You will need to import RCTBridgeModule.h in this header file.

// CalendarManager-Bridging-Header.h +#import "RCTBridgeModule.h"

You can also use RCT_EXTERN_REMAP_MODULE and RCT_EXTERN_REMAP_METHOD to alter the JavaScript name of the module or methods you are exporting. For more information see RCTBridgeModule.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/nativemethodsmixin.html b/releases/0.37/docs/nativemethodsmixin.html new file mode 100644 index 00000000000..41dfbc52571 --- /dev/null +++ b/releases/0.37/docs/nativemethodsmixin.html @@ -0,0 +1,40 @@ +NativeMethodsMixin

NativeMethodsMixin #

NativeMethodsMixin provides methods to access the underlying native +component directly. This can be useful in cases when you want to focus +a view or measure its on-screen dimensions, for example.

The methods described here are available on most of the default components +provided by React Native. Note, however, that they are not available on +composite components that aren't directly backed by a native view. This will +generally include most components that you define in your own app. For more +information, see Direct +Manipulation.

Methods #

static measure(callback) #

Determines the location on screen, width, and height of the given view and +returns the values via an async callback. If successful, the callback will +be called with the following arguments:

  • x
  • y
  • width
  • height
  • pageX
  • pageY

Note that these measurements are not available until after the rendering +has been completed in native. If you need the measurements as soon as +possible, consider using the onLayout +prop instead.

static measureInWindow(callback) #

Determines the location of the given view in the window and returns the +values via an async callback. If the React root view is embedded in +another native view, this will give you the absolute coordinates. If +successful, the callback will be called with the following +arguments:

  • x
  • y
  • width
  • height

Note that these measurements are not available until after the rendering +has been completed in native.

static measureLayout(relativeToNativeNode, onSuccess, onFail) #

Like measure(), but measures the view relative an ancestor, +specified as relativeToNativeNode. This means that the returned x, y +are relative to the origin x, y of the ancestor view.

As always, to obtain a native node handle for a component, you can use +React.findNodeHandle(component).

static focus(0) #

Requests focus for the given input or view. The exact behavior triggered +will depend on the platform and type of view.

static blur(0) #

Removes focus from an input or view. This is the opposite of focus().

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/navigation.html b/releases/0.37/docs/navigation.html new file mode 100644 index 00000000000..554759607ba --- /dev/null +++ b/releases/0.37/docs/navigation.html @@ -0,0 +1,237 @@ +Navigation

Navigation #

This guide covers the various navigation components available in React Native. If you are just getting started with navigation, you will probably want to use Navigator. If you are only targeting iOS and would like to stick to the native look and feel, check out NavigatorIOS. If you are looking for greater control over your navigation stack, you can't go wrong with NavigationExperimental.

Navigator #

Navigator provides a JavaScript implementation of a navigation stack, so it works on both iOS and Android and is easy to customize. This is the same component you used to build your first navigation stack in the navigators tutorial.

Navigator can easily be adapted to render different components based on the current route in its renderScene function. It will transition new scenes onto the screen by sliding in from the right by default, but you can control this behavior by using the configureScene function. You can also configure a navigation bar through the navigationBar prop.

Check out the Navigator API reference for specific examples that cover each of these scenarios.

NavigatorIOS #

If you are targeting iOS only, you may also want to consider using NavigatorIOS. It looks and feels just like UINavigationController, because it is actually built on top of it.

<NavigatorIOS + initialRoute={{ + component: MyScene, + title: 'My Initial Scene', + passProps: { myProp: 'foo' }, + }} +/>

Just like Navigator, NavigatorIOS uses routes to represent scenes, with some important differences. The actual component that will be rendered can be specified using the component key in the route, and any props that should be passed to this component can be specified in passProps. A "navigator" object is automatically passed as a prop to the component, allowing you to call push and pop as needed.

As NavigatorIOS leverages native UIKit navigation, it will automatically render a navigation bar with a back button and title.

import React, { Component, PropTypes } from 'react'; +import { NavigatorIOS, Text, TouchableHighlight, View } from 'react-native'; + +export default class NavigatorIOSApp extends Component { + render() { + return ( + <NavigatorIOS + initialRoute={{ + component: MyScene, + title: 'My Initial Scene', + }} + style={{flex: 1}} + /> + ) + } +} + +class MyScene extends Component { + static propTypes = { + title: PropTypes.string.isRequired, + navigator: PropTypes.object.isRequired, + } + + constructor(props, context) { + super(props, context); + this._onForward = this._onForward.bind(this); + } + + _onForward() { + this.props.navigator.push({ + title: 'Scene ' + nextIndex, + }); + } + + render() { + return ( + <View> + <Text>Current Scene: { this.props.title }</Text> + <TouchableHighlight onPress={this._onForward}> + <Text>Tap me to load the next scene</Text> + </TouchableHighlight> + </View> + ) + } +}

Check out the NavigatorIOS reference docs to learn more about this component.

You may also want to check out react-native-navigation, a component that aims to provide native navigation on both iOS and Android.

NavigationExperimental #

Navigator and NavigatorIOS are both stateful components. If your app has multiple of these, it can become tricky to coordinate navigation transitions between them. NavigationExperimental provides a different approach to navigation, allowing any view to act as a navigation view and using reducers to manipulate state at a top-level object. It is bleeding edge as the name implies, but you might want to check it out if you are craving greater control over your app's navigation.

<NavigationCardStack + onNavigateBack={onPopRouteFunc} + navigationState={myNavigationState} + renderScene={renderSceneFun} +/>

You can import NavigationExperimental like any other component in React Native. Once you have that, you can deconstruct any additional components from NavigationExperimental that you may find useful. Since I am feeling like building navigation stacks today, I'll go ahead and pick out NavigationCardStack and NavigationStateUtils.

import React, { Component } from 'react'; +import { NavigationExperimental } from 'react-native'; + +const { + CardStack: NavigationCardStack, + StateUtils: NavigationStateUtils, +} = NavigationExperimental;

As I said earlier, NavigationExperimental takes a different approach than Navigator and NavigatorIOS. Using it to build a navigation stack requires a few more steps than the stateful components, but the payoff is worth it.

Step 1. Define Initial State and Top Level Component #

Create a new component for your application. This will be the top-level object, so we will define the initial state here. The navigation state will be defined in the navigationState key, where we define our initial route:

class BleedingEdgeApplication extends Component { + constructor(props, context) { + super(props, context); + + this.state = { + // This defines the initial navigation state. + navigationState: { + index: 0, // Starts with first route focused. + routes: [{key: 'My Initial Scene'}], // Starts with only one route. + }, + }; + + // We'll define this function later - hang on + this._onNavigationChange = this._onNavigationChange.bind(this); + } + + _onNavigationChange(type) { + // It's literally the next step. We'll get to it! + } + + render() { + return ( + <Text>This is a placeholder. We will come back to this and render our navigation here later.</Text> + ); + } +}

Alright, now we have a simple stateful component that doesn't do much at all. We can change that. Our initial state contains one route, and the current index. That looks suspiciously just like our initial route definition in Navigator. Do you remember which actions its navigator object provided?

Push and pop, of course. That seems pretty straightforward to implement. I promised you earlier we would be using reducers to manage state at the top-level object. Sit tight.

Step 2. Reducing the Navigation State #

NavigationExperimental comes built-in with a some useful reducers, and they are all available as part of NavigationStateUtils. The two we will be using right now are called -- yep -- push and pop. They take a navigationState object, and return a new navigationState object.

We can use them to write our _onNavigationChange function which, given a "push" or "pop" action, will reduce the state accordingly.

_onNavigationChange(type) { + // Extract the navigationState from the current state: + let {navigationState} = this.state; + + switch (type) { + case 'push': + // Push a new route, which in our case is an object with a key value. + // I am fond of cryptic keys (but seriously, keys should be unique) + const route = {key: 'Route-' + Date.now()}; + + // Use the push reducer provided by NavigationStateUtils + navigationState = NavigationStateUtils.push(navigationState, route); + break; + + case 'pop': + // Pop the current route using the pop reducer. + navigationState = NavigationStateUtils.pop(navigationState); + break; + } + + // NavigationStateUtils gives you back the same `navigationState` if nothing + // has changed. We will only update state if it has changed. + if (this.state.navigationState !== navigationState) { + // Always use setState() when setting a new state! + this.setState({navigationState}); + // If you are new to ES6, the above is equivalent to: + // this.setState({navigationState: navigationState}); + } +}

Cool. I'm getting the hang of this. This is the heart of NavigationExperimental. We are only handling two actions here, but a more complex application could also take into account a "back" action (e.g. Android back button), as well as handle the transition between several tabs in a tabbed application.

I am still missing the initial scene that will be rendered (as well as the actual navigator that will wrap it, but let's not get ahead of ourselves).

Step 3. Define Scenes #

First I want to define a Row component out of convenience. It displays some text and can call some function when pressed.

class TappableRow extends Component { + render() { + return ( + <TouchableHighlight + style={styles.row} + underlayColor="#D0D0D0" + onPress={this.props.onPress}> + <Text style={styles.buttonText}> + {this.props.text} + </Text> + </TouchableHighlight> + ); + } +}

Now I will define my actual scene. It uses a scroll view to display a vertical list of items. The first row displays the current route's key, and two more rows will call our theoretical navigator's push and pop functions.

class MyVeryComplexScene extends Component { + render() { + return ( + <ScrollView style={styles.scrollView}> + <Text style={styles.row}> + Route: {this.props.route.key} + </Text> + <TappableRow + text="Tap me to load the next scene" + onPress={this.props.onPushRoute} + /> + <TappableRow + text="Tap me to go back" + onPress={this.props.onPopRoute} + /> + </ScrollView> + ); + } +}

Step 4. Create a Navigation Stack #

Now that I have defined the state and a function to manage it, I think I can go ahead and create a proper navigator component now. While I'm at it, I'll render my scene after configuring it with the current route's props.

class MyVerySimpleNavigator extends Component { + + // This sets up the methods (e.g. Pop, Push) for navigation. + constructor(props, context) { + super(props, context); + + this._onPushRoute = this.props.onNavigationChange.bind(null, 'push'); + this._onPopRoute = this.props.onNavigationChange.bind(null, 'pop'); + + this._renderScene = this._renderScene.bind(this); + } + + // Now we finally get to use the `NavigationCardStack` to render the scenes. + render() { + return ( + <NavigationCardStack + onNavigateBack={this._onPopRoute} + navigationState={this.props.navigationState} + renderScene={this._renderScene} + style={styles.navigator} + /> + ); + } + + // Render a scene for route. + // The detailed spec of `sceneProps` is defined at `NavigationTypeDefinition` + // as type `NavigationSceneRendererProps`. + // Here you could choose to render a different component for each route, but + // we'll keep it simple. + _renderScene(sceneProps) { + return ( + <MyVeryComplexScene + route={sceneProps.scene.route} + onPushRoute={this._onPushRoute} + onPopRoute={this._onPopRoute} + onExit={this.props.onExit} + /> + ); + } +}

That's it -- so close to the finish line I can smell it. Let's plug our new navigator into our top-level component:

class BleedingEdgeApplication extends Component { + + // constructor and other methods omitted for clarity + + render() { + return ( + <MyVerySimpleNavigator + navigationState={this.state.navigationState} + onNavigationChange={this._onNavigationChange} + onExit={this._exit} + /> + ); + } +}

We're done! Bask in the glory of NavigationExperimental.

Hey -- I think you are missing something. #

(Oh yes, sorry about that -- here's our missing imports and styles.)

import { NavigationExperimental, PixelRatio, ScrollView, StyleSheet, Text, TouchableHighlight } from 'react-native'; + +const styles = StyleSheet.create({ + navigator: { + flex: 1, + }, + scrollView: { + marginTop: 64 + }, + row: { + padding: 15, + backgroundColor: 'white', + borderBottomWidth: 1 / PixelRatio.get(), + borderBottomColor: '#CDCDCD', + }, + rowText: { + fontSize: 17, + }, + buttonText: { + fontSize: 17, + fontWeight: '500', + }, +});

Homework #

You are now an expert navigator. Take a look at NavigationExperimental in UIExplorer to learn how to implement other types of navigation hierarchies, such as a tabbed application with multiple navigation stacks.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/navigator.html b/releases/0.37/docs/navigator.html new file mode 100644 index 00000000000..f0e00d7c660 --- /dev/null +++ b/releases/0.37/docs/navigator.html @@ -0,0 +1,150 @@ +Navigator

Navigator #

Navigator handles the transition between different scenes in your app. +It is implemented in JavaScript and is available on both iOS and Android. If +you are targeting iOS only, you may also want to consider using +NavigatorIOS as it leverages native UIKit +navigation.

To set up the Navigator you provide one or more objects called routes, +to identify each scene. You also provide a renderScene function that +renders the scene for each route object.

import React, { Component } from 'react'; +import { Text, Navigator, TouchableHighlight } from 'react-native'; + +export default class NavAllDay extends Component { + render() { + return ( + <Navigator + initialRoute={{ title: 'Awesome Scene', index: 0 }} + renderScene={(route, navigator) => + <Text>Hello {route.title}!</Text> + } + style={{padding: 100}} + /> + ); + } +}

In the above example, initialRoute is used to specify the first route. It +contains a title property that identifies the route. The renderScene +prop returns a function that displays text based on the route's title.

Additional Scenes #

The first example demonstrated one scene. To set up multiple scenes, you pass +the initialRouteStack prop to Navigator:

render() { + const routes = [ + {title: 'First Scene', index: 0}, + {title: 'Second Scene', index: 1}, + ]; + return ( + <Navigator + initialRoute={routes[0]} + initialRouteStack={routes} + renderScene={(route, navigator) => + <TouchableHighlight onPress={() => { + if (route.index === 0) { + navigator.push(routes[1]); + } else { + navigator.pop(); + } + }}> + <Text>Hello {route.title}!</Text> + </TouchableHighlight> + } + style={{padding: 100}} + /> + ); +}

In the above example, a routes variable is defined with two route objects +representing two scenes. Each route has an index property that is used to +manage the scene being rendered. The renderScene method is changed to +either push or pop the navigator depending on the current route's index. +Finally, the Text component in the scene is now wrapped in a +TouchableHighlight component to help trigger the navigator transitions.

Navigation Bar #

You can optionally pass in your own navigation bar by returning a +Navigator.NavigationBar component to the navigationBar prop in +Navigator. You can configure the navigation bar properties, through +the routeMapper prop. There you set up the left, right, and title +properties of the navigation bar:

<Navigator + renderScene={(route, navigator) => + // ... + } + navigationBar={ + <Navigator.NavigationBar + routeMapper={{ + LeftButton: (route, navigator, index, navState) => + { return (<Text>Cancel</Text>); }, + RightButton: (route, navigator, index, navState) => + { return (<Text>Done</Text>); }, + Title: (route, navigator, index, navState) => + { return (<Text>Awesome Nav Bar</Text>); }, + }} + style={{backgroundColor: 'gray'}} + /> + } +/>

When configuring the left, right, and title items for the navigation bar, +you have access to info such as the current route object and navigation +state. This allows you to customize the title for each scene as well as +the buttons. For example, you can choose to hide the left button for one of +the scenes.

Typically you want buttons to represent the left and right buttons. Building +on the previous example, you can set this up as follows:

LeftButton: (route, navigator, index, navState) => + { + if (route.index === 0) { + return null; + } else { + return ( + <TouchableHighlight onPress={() => navigator.pop()}> + <Text>Back</Text> + </TouchableHighlight> + ); + } + },

This sets up a left navigator bar button that's visible on scenes after the +the first one. When the button is tapped the navigator is popped.

Another type of navigation bar, with breadcrumbs, is provided by +Navigator.BreadcrumbNavigationBar. You can also provide your own navigation +bar by passing it through the navigationBar prop. See the +UIExplorer +demo to try out both built-in navigation bars out and see how to use them.

Scene Transitions #

To change the animation or gesture properties of the scene, provide a +configureScene prop to get the config object for a given route:

<Navigator + renderScene={(route, navigator) => + // ... + } + configureScene={(route, routeStack) => + Navigator.SceneConfigs.FloatFromBottom} +/>

In the above example, the newly pushed scene will float up from the bottom. +See Navigator.SceneConfigs for default animations and more info on +available scene config options.

Props #

configureScene function #

Optional function where you can configure scene animations and +gestures. Will be invoked with route and routeStack parameters, +where route corresponds to the current scene being rendered by the +Navigator and routeStack is the set of currently mounted routes +that the navigator could transition to.

The function should return a scene configuration object.

(route, routeStack) => Navigator.SceneConfigs.FloatFromRight

Available scene configuration options are:

  • Navigator.SceneConfigs.PushFromRight (default)
  • Navigator.SceneConfigs.FloatFromRight
  • Navigator.SceneConfigs.FloatFromLeft
  • Navigator.SceneConfigs.FloatFromBottom
  • Navigator.SceneConfigs.FloatFromBottomAndroid
  • Navigator.SceneConfigs.FadeAndroid
  • Navigator.SceneConfigs.HorizontalSwipeJump
  • Navigator.SceneConfigs.HorizontalSwipeJumpFromRight
  • Navigator.SceneConfigs.HorizontalSwipeJumpFromLeft
  • Navigator.SceneConfigs.VerticalUpSwipeJump
  • Navigator.SceneConfigs.VerticalDownSwipeJump

initialRoute object #

The initial route for navigation. A route is an object that the navigator +will use to identify each scene it renders.

If both initialRoute and initialRouteStack props are passed to +Navigator, then initialRoute must be in a route in +initialRouteStack. If initialRouteStack is passed as a prop but +initialRoute is not, then initialRoute will default internally to +the last item in initialRouteStack.

initialRouteStack [object] #

Pass this in to provide a set of routes to initially mount. This prop +is required if initialRoute is not provided to the navigator. If this +prop is not passed in, it will default internally to an array +containing only initialRoute.

navigationBar node #

Use this to provide an optional component representing a navigation bar +that is persisted across scene transitions. This component will receive +two props: navigator and navState representing the navigator +component and its state. The component is re-rendered when the route +changes.

navigator object #

Optionally pass in the navigator object from a parent Navigator.

onDidFocus function #

Will be called with the new route of each scene after the transition is +complete or after the initial mounting.

onWillFocus function #

Pass in a function to get notified with the target route when +the navigator component is mounted and before each navigator transition.

renderScene function #

Required function which renders the scene for a given route. Will be +invoked with the route and the navigator object.

(route, navigator) => + <MySceneComponent title={route.title} navigator={navigator} />

sceneStyle View#style #

Styles to apply to the container of each scene.

Methods #

immediatelyResetRouteStack(nextRouteStack) #

Reset every scene with an array of routes.

Parameters:
Name and TypeDescription
nextRouteStack

RouteStack

Next route stack to reinitialize. +All existing route stacks are destroyed an potentially recreated. There +is no accompanying animation and this method immediately replaces and +re-renders the navigation bar and the stack items.

jumpTo(route) #

Transition to an existing scene without unmounting.

Parameters:
Name and TypeDescription
route

object

Route to transition to. The specified route must +be in the currently mounted set of routes defined in routeStack.

jumpForward(0) #

Jump forward to the next scene in the route stack.

jumpBack(0) #

Jump backward without unmounting the current scene.

push(route) #

Navigate forward to a new scene, squashing any scenes that you could +jump forward to.

Parameters:
Name and TypeDescription
route

object

Route to push into the navigator stack.

popN(n) #

Go back N scenes at once. When N=1, behavior matches pop(). +When N is invalid(negative or bigger than current routes count), do nothing.

Parameters:
Name and TypeDescription
n

number

The number of scenes to pop. Should be an integer.

pop(0) #

Transition back and unmount the current scene.

replaceAtIndex(route, index, cb) #

Replace a scene as specified by an index.

Parameters:
Name and TypeDescription
route

object

Route representing the new scene to render.

index

number

The route in the stack that should be replaced. + If negative, it counts from the back of the stack.

cb

Function

Callback function when the scene has been replaced.

replace(route) #

Replace the current scene with a new route.

Parameters:
Name and TypeDescription
route

object

Route that replaces the current scene.

replacePrevious(route) #

Replace the previous scene.

Parameters:
Name and TypeDescription
route

object

Route that replaces the previous scene.

popToTop(0) #

Pop to the first scene in the stack, unmounting every other scene.

popToRoute(route) #

Pop to a particular scene, as specified by its route. +All scenes after it will be unmounted.

Parameters:
Name and TypeDescription
route

object

Route to pop to.

replacePreviousAndPop(route) #

Replace the previous scene and pop to it.

Parameters:
Name and TypeDescription
route

object

Route that replaces the previous scene.

resetTo(route) #

Navigate to a new scene and reset route stack.

Parameters:
Name and TypeDescription
route

object

Route to navigate to.

getCurrentRoutes(0) #

Returns the current list of routes.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/navigatorios.html b/releases/0.37/docs/navigatorios.html new file mode 100644 index 00000000000..12b1a7464c7 --- /dev/null +++ b/releases/0.37/docs/navigatorios.html @@ -0,0 +1,449 @@ +NavigatorIOS

NavigatorIOS #

NavigatorIOS is a wrapper around +UINavigationController, +enabling you to implement a navigation stack. It works exactly the same as it +would on a native app using UINavigationController, providing the same +animations and behavior from UIKIt.

As the name implies, it is only available on iOS. Take a look at +Navigator for a similar solution for your +cross-platform needs, or check out +react-native-navigation, a +component that aims to provide native navigation on both iOS and Android.

To set up the navigator, provide the initialRoute prop with a route +object. A route object is used to describe each scene that your app +navigates to. initialRoute represents the first route in your navigator.

import React, { Component, PropTypes } from 'react'; +import { NavigatorIOS, Text } from 'react-native'; + +export default class NavigatorIOSApp extends Component { + render() { + return ( + <NavigatorIOS + initialRoute={{ + component: MyScene, + title: 'My Initial Scene', + }} + style={{flex: 1}} + /> + ); + } +} + +class MyScene extends Component { + static propTypes = { + title: PropTypes.string.isRequired, + navigator: PropTypes.object.isRequired, + } + + _onForward = () => { + this.props.navigator.push({ + title: 'Scene ' + nextIndex, + }); + } + + render() { + return ( + <View> + <Text>Current Scene: { this.props.title }</Text> + <TouchableHighlight onPress={this._onForward}> + <Text>Tap me to load the next scene</Text> + </TouchableHighlight> + </View> + ) + } +}

In this code, the navigator renders the component specified in initialRoute, +which in this case is MyScene. This component will receive a route prop +and a navigator prop representing the navigator. The navigator's navigation +bar will render the title for the current scene, "My Initial Scene".

You can optionally pass in a passProps property to your initialRoute. +NavigatorIOS passes this in as props to the rendered component:

initialRoute={{ + component: MyScene, + title: 'My Initial Scene', + passProps: { myProp: 'foo' } +}}

You can then access the props passed in via {this.props.myProp}.

Handling Navigation #

To trigger navigation functionality such as pushing or popping a view, you +have access to a navigator object. The object is passed in as a prop to any +component that is rendered by NavigatorIOS. You can then call the +relevant methods to perform the navigation action you need:

class MyView extends Component { + _handleBackPress() { + this.props.navigator.pop(); + } + + _handleNextPress(nextRoute) { + this.props.navigator.push(nextRoute); + } + + render() { + const nextRoute = { + component: MyView, + title: 'Bar That', + passProps: { myProp: 'bar' } + }; + return( + <TouchableHighlight onPress={() => this._handleNextPress(nextRoute)}> + <Text style={{marginTop: 200, alignSelf: 'center'}}> + See you on the other nav {this.props.myProp}! + </Text> + </TouchableHighlight> + ); + } +}

You can also trigger navigator functionality from the NavigatorIOS +component:

class NavvyIOS extends Component { + _handleNavigationRequest() { + this.refs.nav.push({ + component: MyView, + title: 'Genius', + passProps: { myProp: 'genius' }, + }); + } + + render() { + return ( + <NavigatorIOS + ref='nav' + initialRoute={{ + component: MyView, + title: 'Foo This', + passProps: { myProp: 'foo' }, + rightButtonTitle: 'Add', + onRightButtonPress: () => this._handleNavigationRequest(), + }} + style={{flex: 1}} + /> + ); + } +}

The code above adds a _handleNavigationRequest private method that is +invoked from the NavigatorIOS component when the right navigation bar item +is pressed. To get access to the navigator functionality, a reference to it +is saved in the ref prop and later referenced to push a new scene into the +navigation stack.

Navigation Bar Configuration #

Props passed to NavigatorIOS will set the default configuration +for the navigation bar. Props passed as properties to a route object will set +the configuration for that route's navigation bar, overriding any props +passed to the NavigatorIOS component.

_handleNavigationRequest() { + this.refs.nav.push({ + //... + passProps: { myProp: 'genius' }, + barTintColor: '#996699', + }); +} + +render() { + return ( + <NavigatorIOS + //... + style={{flex: 1}} + barTintColor='#ffffcc' + /> + ); +}

In the example above the navigation bar color is changed when the new route +is pushed.

Props #

barTintColor string #

The default background color of the navigation bar.

initialRoute {component: function, title: string, titleImage: Image.propTypes.source, passProps: object, backButtonIcon: Image.propTypes.source, backButtonTitle: string, leftButtonIcon: Image.propTypes.source, leftButtonTitle: string, leftButtonSystemIcon: Object.keys(SystemIcons), onLeftButtonPress: function, rightButtonIcon: Image.propTypes.source, rightButtonTitle: string, rightButtonSystemIcon: Object.keys(SystemIcons), onRightButtonPress: function, wrapperStyle: [object Object], navigationBarHidden: bool, shadowHidden: bool, tintColor: string, barTintColor: string, titleTextColor: string, translucent: bool} #

NavigatorIOS uses route objects to identify child views, their props, +and navigation bar configuration. Navigation operations such as push +operations expect routes to look like this the initialRoute.

interactivePopGestureEnabled bool #

Boolean value that indicates whether the interactive pop gesture is +enabled. This is useful for enabling/disabling the back swipe navigation +gesture.

If this prop is not provided, the default behavior is for the back swipe +gesture to be enabled when the navigation bar is shown and disabled when +the navigation bar is hidden. Once you've provided the +interactivePopGestureEnabled prop, you can never restore the default +behavior.

itemWrapperStyle View#style #

The default wrapper style for components in the navigator. +A common use case is to set the backgroundColor for every scene.

navigationBarHidden bool #

Boolean value that indicates whether the navigation bar is hidden +by default.

shadowHidden bool #

Boolean value that indicates whether to hide the 1px hairline shadow +by default.

tintColor string #

The default color used for the buttons in the navigation bar.

titleTextColor string #

The default text color of the navigation bar title.

translucent bool #

Boolean value that indicates whether the navigation bar is +translucent by default

Methods #

push(route) #

Navigate forward to a new route.

Parameters:
Name and TypeDescription
route

object

The new route to navigate to.

popN(n) #

Go back N scenes at once. When N=1, behavior matches pop().

Parameters:
Name and TypeDescription
n

number

The number of scenes to pop.

pop(0) #

Pop back to the previous scene.

replaceAtIndex(route, index) #

Replace a route in the navigation stack.

Parameters:
Name and TypeDescription
route

object

The new route that will replace the specified one.

index

number

The route into the stack that should be replaced. + If it is negative, it counts from the back of the stack.

replace(route) #

Replace the route for the current scene and immediately +load the view for the new route.

Parameters:
Name and TypeDescription
route

object

The new route to navigate to.

replacePrevious(route) #

Replace the route/view for the previous scene.

Parameters:
Name and TypeDescription
route

object

The new route to will replace the previous scene.

popToTop(0) #

Go back to the topmost item in the navigation stack.

popToRoute(route) #

Go back to the item for a particular route object.

Parameters:
Name and TypeDescription
route

object

The new route to navigate to.

replacePreviousAndPop(route) #

Replaces the previous route/view and transitions back to it.

Parameters:
Name and TypeDescription
route

object

The new route that replaces the previous scene.

resetTo(route) #

Replaces the top item and pop to it.

Parameters:
Name and TypeDescription
route

object

The new route that will replace the topmost item.

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +const React = require('react'); +const ReactNative = require('react-native'); +const ViewExample = require('./ViewExample'); +const createExamplePage = require('./createExamplePage'); +const { + AlertIOS, + NavigatorIOS, + ScrollView, + StyleSheet, + Text, + TouchableHighlight, + View, +} = ReactNative; + +class EmptyPage extends React.Component { + render() { + return ( + <View style={styles.emptyPage}> + <Text style={styles.emptyPageText}> + {this.props.text} + </Text> + </View> + ); + } +} + +class NavigatorIOSExamplePage extends React.Component { + render() { + var recurseTitle = 'Recurse Navigation'; + if (!this.props.depth || this.props.depth === 1) { + recurseTitle += ' - more examples here'; + } + return ( + <ScrollView style={styles.list}> + <View style={styles.line}/> + <View style={styles.group}> + {this._renderRow(recurseTitle, () => { + this.props.navigator.push({ + title: NavigatorIOSExample.title, + component: NavigatorIOSExamplePage, + backButtonTitle: 'Custom Back', + passProps: {depth: this.props.depth ? this.props.depth + 1 : 1}, + }); + })} + {this._renderRow('Push View Example', () => { + this.props.navigator.push({ + title: 'Very Long Custom View Example Title', + component: createExamplePage(null, ViewExample), + }); + })} + {this._renderRow('Custom title image Example', () => { + this.props.navigator.push({ + title: 'Custom title image Example', + titleImage: require('./relay.png'), + component: createExamplePage(null, ViewExample), + }); + })} + {this._renderRow('Custom Right Button', () => { + this.props.navigator.push({ + title: NavigatorIOSExample.title, + component: EmptyPage, + rightButtonTitle: 'Cancel', + onRightButtonPress: () => this.props.navigator.pop(), + passProps: { + text: 'This page has a right button in the nav bar', + } + }); + })} + {this._renderRow('Custom Right System Button', () => { + this.props.navigator.push({ + title: NavigatorIOSExample.title, + component: EmptyPage, + rightButtonSystemIcon: 'bookmarks', + onRightButtonPress: () => this.props.navigator.pop(), + passProps: { + text: 'This page has a right system button in the nav bar', + } + }); + })} + {this._renderRow('Custom Left & Right Icons', () => { + this.props.navigator.push({ + title: NavigatorIOSExample.title, + component: EmptyPage, + leftButtonTitle: 'Custom Left', + onLeftButtonPress: () => this.props.navigator.pop(), + rightButtonIcon: require('image!NavBarButtonPlus'), + onRightButtonPress: () => { + AlertIOS.alert( + 'Bar Button Action', + 'Recognized a tap on the bar button icon', + [ + { + text: 'OK', + onPress: () => console.log('Tapped OK'), + }, + ] + ); + }, + passProps: { + text: 'This page has an icon for the right button in the nav bar', + } + }); + })} + {this._renderRow('Custom Left & Right System Icons', () => { + this.props.navigator.push({ + title: NavigatorIOSExample.title, + component: EmptyPage, + leftButtonSystemIcon: 'cancel', + onLeftButtonPress: () => this.props.navigator.pop(), + rightButtonSystemIcon: 'search', + onRightButtonPress: () => { + AlertIOS.alert( + 'Bar Button Action', + 'Recognized a tap on the bar button icon', + [ + { + text: 'OK', + onPress: () => console.log('Tapped OK'), + }, + ] + ); + }, + passProps: { + text: 'This page has an icon for the right button in the nav bar', + } + }); + })} + {this._renderRow('Pop', () => { + this.props.navigator.pop(); + })} + {this._renderRow('Pop to top', () => { + this.props.navigator.popToTop(); + })} + {this._renderReplace()} + {this._renderReplacePrevious()} + {this._renderReplacePreviousAndPop()} + {this._renderRow('Exit NavigatorIOS Example', this.props.onExampleExit)} + </View> + <View style={styles.line}/> + </ScrollView> + ); + } + + _renderReplace = () => { + if (!this.props.depth) { + // this is to avoid replacing the top of the stack + return null; + } + return this._renderRow('Replace here', () => { + var prevRoute = this.props.route; + this.props.navigator.replace({ + title: 'New Navigation', + component: EmptyPage, + rightButtonTitle: 'Undo', + onRightButtonPress: () => this.props.navigator.replace(prevRoute), + passProps: { + text: 'The component is replaced, but there is currently no ' + + 'way to change the right button or title of the current route', + } + }); + }); + }; + + _renderReplacePrevious = () => { + if (!this.props.depth || this.props.depth < 2) { + // this is to avoid replacing the top of the stack + return null; + } + return this._renderRow('Replace previous', () => { + this.props.navigator.replacePrevious({ + title: 'Replaced', + component: EmptyPage, + passProps: { + text: 'This is a replaced "previous" page', + }, + wrapperStyle: styles.customWrapperStyle, + }); + }); + }; + + _renderReplacePreviousAndPop = () => { + if (!this.props.depth || this.props.depth < 2) { + // this is to avoid replacing the top of the stack + return null; + } + return this._renderRow('Replace previous and pop', () => { + this.props.navigator.replacePreviousAndPop({ + title: 'Replaced and Popped', + component: EmptyPage, + passProps: { + text: 'This is a replaced "previous" page', + }, + wrapperStyle: styles.customWrapperStyle, + }); + }); + }; + + _renderRow = (title: string, onPress: Function) => { + return ( + <View> + <TouchableHighlight onPress={onPress}> + <View style={styles.row}> + <Text style={styles.rowText}> + {title} + </Text> + </View> + </TouchableHighlight> + <View style={styles.separator} /> + </View> + ); + }; +} + +class NavigatorIOSExample extends React.Component { + static title = '<NavigatorIOS>'; + static description = 'iOS navigation capabilities'; + static external = true; + + render() { + const {onExampleExit} = this.props; + return ( + <NavigatorIOS + style={styles.container} + initialRoute={{ + title: NavigatorIOSExample.title, + component: NavigatorIOSExamplePage, + passProps: {onExampleExit}, + }} + tintColor="#008888" + /> + ); + } +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + customWrapperStyle: { + backgroundColor: '#bbdddd', + }, + emptyPage: { + flex: 1, + paddingTop: 64, + }, + emptyPageText: { + margin: 10, + }, + list: { + backgroundColor: '#eeeeee', + marginTop: 10, + }, + group: { + backgroundColor: 'white', + }, + groupSpace: { + height: 15, + }, + line: { + backgroundColor: '#bbbbbb', + height: StyleSheet.hairlineWidth, + }, + row: { + backgroundColor: 'white', + justifyContent: 'center', + paddingHorizontal: 15, + paddingVertical: 15, + }, + separator: { + height: StyleSheet.hairlineWidth, + backgroundColor: '#bbbbbb', + marginLeft: 15, + }, + rowNote: { + fontSize: 17, + }, + rowText: { + fontSize: 17, + fontWeight: '500', + }, +}); + +module.exports = NavigatorIOSExample;
\ No newline at end of file diff --git a/releases/0.37/docs/netinfo.html b/releases/0.37/docs/netinfo.html new file mode 100644 index 00000000000..d5526e7a33d --- /dev/null +++ b/releases/0.37/docs/netinfo.html @@ -0,0 +1,234 @@ +NetInfo

NetInfo #

NetInfo exposes info about online/offline status

NetInfo.fetch().done((reach) => { + console.log('Initial: ' + reach); +}); +function handleFirstConnectivityChange(reach) { + console.log('First change: ' + reach); + NetInfo.removeEventListener( + 'change', + handleFirstConnectivityChange + ); +} +NetInfo.addEventListener( + 'change', + handleFirstConnectivityChange +);

IOS #

Asynchronously determine if the device is online and on a cellular network.

  • none - device is offline
  • wifi - device is online and connected via wifi, or is the iOS simulator
  • cell - device is connected via Edge, 3G, WiMax, or LTE
  • unknown - error case and the network status is unknown

Android #

To request network info, you need to add the following line to your +app's AndroidManifest.xml:

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> +Asynchronously determine if the device is connected and details about that connection.

Android Connectivity Types.

  • NONE - device is offline
  • BLUETOOTH - The Bluetooth data connection.
  • DUMMY - Dummy data connection.
  • ETHERNET - The Ethernet data connection.
  • MOBILE - The Mobile data connection.
  • MOBILE_DUN - A DUN-specific Mobile data connection.
  • MOBILE_HIPRI - A High Priority Mobile data connection.
  • MOBILE_MMS - An MMS-specific Mobile data connection.
  • MOBILE_SUPL - A SUPL-specific Mobile data connection.
  • VPN - A virtual network using one or more native bearers. Requires API Level 21
  • WIFI - The WIFI data connection.
  • WIMAX - The WiMAX data connection.
  • UNKNOWN - Unknown data connection.

The rest ConnectivityStates are hidden by the Android API, but can be used if necessary.

isConnectionExpensive #

Available on Android. Detect if the current active connection is metered or not. A network is +classified as metered when the user is sensitive to heavy data usage on that connection due to +monetary costs, data limitations or battery/performance issues.

NetInfo.isConnectionExpensive() +.then(isConnectionExpensive => { + console.log('Connection is ' + (isConnectionExpensive ? 'Expensive' : 'Not Expensive')); +}) +.catch(error => { + console.error(error); +});

isConnected #

Available on all platforms. Asynchronously fetch a boolean to determine +internet connectivity.

NetInfo.isConnected.fetch().then(isConnected => { + console.log('First, is ' + (isConnected ? 'online' : 'offline')); +}); +function handleFirstConnectivityChange(isConnected) { + console.log('Then, is ' + (isConnected ? 'online' : 'offline')); + NetInfo.isConnected.removeEventListener( + 'change', + handleFirstConnectivityChange + ); +} +NetInfo.isConnected.addEventListener( + 'change', + handleFirstConnectivityChange +);

Methods #

static addEventListener(eventName, handler) #

Invokes the listener whenever network status changes. +The listener receives one of the connectivity types listed above.

static removeEventListener(eventName, handler) #

Removes the listener for network status changes.

static fetch(0) #

Returns a promise that resolves with one of the connectivity types listed +above.

static isConnectionExpensive(0) #

Properties #

isConnected: ObjectExpression #

An object with the same methods as above but the listener receives a +boolean which represents the internet connectivity. +Use this if you are only interested with whether the device has internet +connectivity.

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +const React = require('react'); +const ReactNative = require('react-native'); +const { + NetInfo, + Text, + View, + TouchableWithoutFeedback, +} = ReactNative; + +class ConnectionInfoSubscription extends React.Component { + state = { + connectionInfoHistory: [], + }; + + componentDidMount() { + NetInfo.addEventListener( + 'change', + this._handleConnectionInfoChange + ); + } + + componentWillUnmount() { + NetInfo.removeEventListener( + 'change', + this._handleConnectionInfoChange + ); + } + + _handleConnectionInfoChange = (connectionInfo) => { + const connectionInfoHistory = this.state.connectionInfoHistory.slice(); + connectionInfoHistory.push(connectionInfo); + this.setState({ + connectionInfoHistory, + }); + }; + + render() { + return ( + <View> + <Text>{JSON.stringify(this.state.connectionInfoHistory)}</Text> + </View> + ); + } +} + +class ConnectionInfoCurrent extends React.Component { + state = { + connectionInfo: null, + }; + + componentDidMount() { + NetInfo.addEventListener( + 'change', + this._handleConnectionInfoChange + ); + NetInfo.fetch().done( + (connectionInfo) => { this.setState({connectionInfo}); } + ); + } + + componentWillUnmount() { + NetInfo.removeEventListener( + 'change', + this._handleConnectionInfoChange + ); + } + + _handleConnectionInfoChange = (connectionInfo) => { + this.setState({ + connectionInfo, + }); + }; + + render() { + return ( + <View> + <Text>{this.state.connectionInfo}</Text> + </View> + ); + } +} + +class IsConnected extends React.Component { + state = { + isConnected: null, + }; + + componentDidMount() { + NetInfo.isConnected.addEventListener( + 'change', + this._handleConnectivityChange + ); + NetInfo.isConnected.fetch().done( + (isConnected) => { this.setState({isConnected}); } + ); + } + + componentWillUnmount() { + NetInfo.isConnected.removeEventListener( + 'change', + this._handleConnectivityChange + ); + } + + _handleConnectivityChange = (isConnected) => { + this.setState({ + isConnected, + }); + }; + + render() { + return ( + <View> + <Text>{this.state.isConnected ? 'Online' : 'Offline'}</Text> + </View> + ); + } +} + +class IsConnectionExpensive extends React.Component { + state = { + isConnectionExpensive: (null : ?boolean), + }; + + _checkIfExpensive = () => { + NetInfo.isConnectionExpensive().then( + isConnectionExpensive => { this.setState({isConnectionExpensive}); } + ); + }; + + render() { + return ( + <View> + <TouchableWithoutFeedback onPress={this._checkIfExpensive}> + <View> + <Text>Click to see if connection is expensive: + {this.state.isConnectionExpensive === true ? 'Expensive' : + this.state.isConnectionExpensive === false ? 'Not expensive' + : 'Unknown'} + </Text> + </View> + </TouchableWithoutFeedback> + </View> + ); + } +} + +exports.title = 'NetInfo'; +exports.description = 'Monitor network status'; +exports.examples = [ + { + title: 'NetInfo.isConnected', + description: 'Asynchronously load and observe connectivity', + render(): React.Element<any> { return <IsConnected />; } + }, + { + title: 'NetInfo.update', + description: 'Asynchronously load and observe connectionInfo', + render(): React.Element<any> { return <ConnectionInfoCurrent />; } + }, + { + title: 'NetInfo.updateHistory', + description: 'Observed updates to connectionInfo', + render(): React.Element<any> { return <ConnectionInfoSubscription />; } + }, + { + platform: 'android', + title: 'NetInfo.isConnectionExpensive (Android)', + description: 'Asynchronously check isConnectionExpensive', + render(): React.Element<any> { return <IsConnectionExpensive />; } + }, +];
\ No newline at end of file diff --git a/releases/0.37/docs/network.html b/releases/0.37/docs/network.html new file mode 100644 index 00000000000..03e3de522fb --- /dev/null +++ b/releases/0.37/docs/network.html @@ -0,0 +1,81 @@ +Networking

Networking #

Many mobile apps need to load resources from a remote URL. You may want to make a POST request to a REST API, or you may simply need to fetch a chunk of static content from another server.

Using Fetch #

React Native provides the Fetch API for your networking needs. Fetch will seem familiar if you have used XMLHttpRequest or other networking APIs before. You may refer to MDN's guide on Using Fetch for additional information.

Making requests #

In order to fetch content from an arbitrary URL, just pass the URL to fetch:

fetch('https://mywebsite.com/mydata.json')

Fetch also takes an optional second argument that allows you to customize the HTTP request. You may want to specify additional headers, or make a POST request:

fetch('https://mywebsite.com/endpoint/', { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + firstParam: 'yourValue', + secondParam: 'yourOtherValue', + }) +})

Take a look at the Fetch Request docs for a full list of properties.

Handling the response #

The above examples show how you can make a request. In many cases, you will want to do something with the response.

Networking is an inherently asynchronous operation. Fetch methods will return a Promise that makes it straightforward to write code that works in an asynchronous manner:

function getMoviesFromApiAsync() { + return fetch('https://facebook.github.io/react-native/movies.json') + .then((response) => response.json()) + .then((responseJson) => { + return responseJson.movies; + }) + .catch((error) => { + console.error(error); + }); + }

You can also use the proposed ES2017 async/await syntax in a React Native app:

async function getMoviesFromApi() { + try { + let response = await fetch('https://facebook.github.io/react-native/movies.json'); + let responseJson = await response.json(); + return responseJson.movies; + } catch(error) { + console.error(error); + } + }

Don't forget to catch any errors that may be thrown by fetch, otherwise they will be dropped silently.

By default, iOS will block any request that's not encrypted using SSL. If you need to fetch from a cleartext URL (one that begins with http) you will first need to add an App Transport Security exception. If you know ahead of time what domains you will need access to, it is more secure to add exceptions just for those domains; if the domains are not known until runtime you can disable ATS completely. See Apple's documentation for more information.

Using Other Networking Libraries #

The XMLHttpRequest API is built in to React Native. This means that you can use third party libraries such as frisbee or axios that depend on it, or you can use the XMLHttpRequest API directly if you prefer.

var request = new XMLHttpRequest(); +request.onreadystatechange = (e) => { + if (request.readyState !== 4) { + return; + } + + if (request.status === 200) { + console.log('success', request.responseText); + } else { + console.warn('error'); + } +}; + +request.open('GET', 'https://mywebsite.com/endpoint/'); +request.send();

The security model for XMLHttpRequest is different than on web as there is no concept of CORS in native apps.

WebSocket Support #

React Native also supports WebSockets, a protocol which provides full-duplex communication channels over a single TCP connection.

var ws = new WebSocket('ws://host.com/path'); + +ws.onopen = () => { + // connection opened + + ws.send('something'); // send a message +}; + +ws.onmessage = (e) => { + // a message was received + console.log(e.data); +}; + +ws.onerror = (e) => { + // an error occurred + console.log(e.message); +}; + +ws.onclose = (e) => { + // connection closed + console.log(e.code, e.reason); +};

Your app can now display all sorts of data and you may soon need to organize this content into several screens. To manage the transition between these screens, you will need to learn about navigators.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/panresponder.html b/releases/0.37/docs/panresponder.html new file mode 100644 index 00000000000..be91d530567 --- /dev/null +++ b/releases/0.37/docs/panresponder.html @@ -0,0 +1,197 @@ +PanResponder

PanResponder #

PanResponder reconciles several touches into a single gesture. It makes +single-touch gestures resilient to extra touches, and can be used to +recognize simple multi-touch gestures.

By default, PanResponder holds an `InteractionManager handle to block +long-running JS events from interrupting active gestures.

It provides a predictable wrapper of the responder handlers provided by the +gesture responder system. +For each handler, it provides a new gestureState object alongside the +native event object:

onPanResponderMove: (event, gestureState) => {}

A native event is a synthetic touch event with the following form:

  • nativeEvent
    • changedTouches - Array of all touch events that have changed since the last event
    • identifier - The ID of the touch
    • locationX - The X position of the touch, relative to the element
    • locationY - The Y position of the touch, relative to the element
    • pageX - The X position of the touch, relative to the root element
    • pageY - The Y position of the touch, relative to the root element
    • target - The node id of the element receiving the touch event
    • timestamp - A time identifier for the touch, useful for velocity calculation
    • touches - Array of all current touches on the screen

A gestureState object has the following:

  • stateID - ID of the gestureState- persisted as long as there at least + one touch on screen
  • moveX - the latest screen coordinates of the recently-moved touch
  • moveY - the latest screen coordinates of the recently-moved touch
  • x0 - the screen coordinates of the responder grant
  • y0 - the screen coordinates of the responder grant
  • dx - accumulated distance of the gesture since the touch started
  • dy - accumulated distance of the gesture since the touch started
  • vx - current velocity of the gesture
  • vy - current velocity of the gesture
  • numberActiveTouches - Number of touches currently on screen

Basic Usage #

componentWillMount: function() { + this._panResponder = PanResponder.create({ + // Ask to be the responder: + onStartShouldSetPanResponder: (evt, gestureState) => true, + onStartShouldSetPanResponderCapture: (evt, gestureState) => true, + onMoveShouldSetPanResponder: (evt, gestureState) => true, + onMoveShouldSetPanResponderCapture: (evt, gestureState) => true, + + onPanResponderGrant: (evt, gestureState) => { + // The guesture has started. Show visual feedback so the user knows + // what is happening! + + // gestureState.d{x,y} will be set to zero now + }, + onPanResponderMove: (evt, gestureState) => { + // The most recent move distance is gestureState.move{X,Y} + + // The accumulated gesture distance since becoming responder is + // gestureState.d{x,y} + }, + onPanResponderTerminationRequest: (evt, gestureState) => true, + onPanResponderRelease: (evt, gestureState) => { + // The user has released all touches while this view is the + // responder. This typically means a gesture has succeeded + }, + onPanResponderTerminate: (evt, gestureState) => { + // Another component has become the responder, so this gesture + // should be cancelled + }, + onShouldBlockNativeResponder: (evt, gestureState) => { + // Returns whether this component should block native components from becoming the JS + // responder. Returns true by default. Is currently only supported on android. + return true; + }, + }); + }, + + render: function() { + return ( + <View {...this._panResponder.panHandlers} /> + ); + },

Working Example #

To see it in action, try the +PanResponder example in UIExplorer

Methods #

static create(config) #

@param {object} config Enhanced versions of all of the responder callbacks +that provide not only the typical ResponderSyntheticEvent, but also the +PanResponder gesture state. Simply replace the word Responder with +PanResponder in each of the typical onResponder* callbacks. For +example, the config object would look like:

  • onMoveShouldSetPanResponder: (e, gestureState) => {...}
  • onMoveShouldSetPanResponderCapture: (e, gestureState) => {...}
  • onStartShouldSetPanResponder: (e, gestureState) => {...}
  • onStartShouldSetPanResponderCapture: (e, gestureState) => {...}
  • onPanResponderReject: (e, gestureState) => {...}
  • onPanResponderGrant: (e, gestureState) => {...}
  • onPanResponderStart: (e, gestureState) => {...}
  • onPanResponderEnd: (e, gestureState) => {...}
  • onPanResponderRelease: (e, gestureState) => {...}
  • onPanResponderMove: (e, gestureState) => {...}
  • onPanResponderTerminate: (e, gestureState) => {...}
  • onPanResponderTerminationRequest: (e, gestureState) => {...}
  • onShouldBlockNativeResponder: (e, gestureState) => {...}

    In general, for events that have capture equivalents, we update the +gestureState once in the capture phase and can use it in the bubble phase +as well.

    Be careful with onStartShould* callbacks. They only reflect updated +gestureState for start/end events that bubble/capture to the Node. +Once the node is the responder, you can rely on every start/end event +being processed by the gesture and gestureState being updated +accordingly. (numberActiveTouches) may not be totally accurate unless you +are the responder.

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + PanResponder, + StyleSheet, + View, +} = ReactNative; + +var CIRCLE_SIZE = 80; + +var PanResponderExample = React.createClass({ + + statics: { + title: 'PanResponder Sample', + description: 'Shows the use of PanResponder to provide basic gesture handling.', + }, + + _panResponder: {}, + _previousLeft: 0, + _previousTop: 0, + _circleStyles: {}, + circle: (null : ?{ setNativeProps(props: Object): void }), + + componentWillMount: function() { + this._panResponder = PanResponder.create({ + onStartShouldSetPanResponder: this._handleStartShouldSetPanResponder, + onMoveShouldSetPanResponder: this._handleMoveShouldSetPanResponder, + onPanResponderGrant: this._handlePanResponderGrant, + onPanResponderMove: this._handlePanResponderMove, + onPanResponderRelease: this._handlePanResponderEnd, + onPanResponderTerminate: this._handlePanResponderEnd, + }); + this._previousLeft = 20; + this._previousTop = 84; + this._circleStyles = { + style: { + left: this._previousLeft, + top: this._previousTop, + backgroundColor: 'green', + } + }; + }, + + componentDidMount: function() { + this._updateNativeStyles(); + }, + + render: function() { + return ( + <View + style={styles.container}> + <View + ref={(circle) => { + this.circle = circle; + }} + style={styles.circle} + {...this._panResponder.panHandlers} + /> + </View> + ); + }, + + _highlight: function() { + this._circleStyles.style.backgroundColor = 'blue'; + this._updateNativeStyles(); + }, + + _unHighlight: function() { + this._circleStyles.style.backgroundColor = 'green'; + this._updateNativeStyles(); + }, + + _updateNativeStyles: function() { + this.circle && this.circle.setNativeProps(this._circleStyles); + }, + + _handleStartShouldSetPanResponder: function(e: Object, gestureState: Object): boolean { + // Should we become active when the user presses down on the circle? + return true; + }, + + _handleMoveShouldSetPanResponder: function(e: Object, gestureState: Object): boolean { + // Should we become active when the user moves a touch over the circle? + return true; + }, + + _handlePanResponderGrant: function(e: Object, gestureState: Object) { + this._highlight(); + }, + _handlePanResponderMove: function(e: Object, gestureState: Object) { + this._circleStyles.style.left = this._previousLeft + gestureState.dx; + this._circleStyles.style.top = this._previousTop + gestureState.dy; + this._updateNativeStyles(); + }, + _handlePanResponderEnd: function(e: Object, gestureState: Object) { + this._unHighlight(); + this._previousLeft += gestureState.dx; + this._previousTop += gestureState.dy; + }, +}); + +var styles = StyleSheet.create({ + circle: { + width: CIRCLE_SIZE, + height: CIRCLE_SIZE, + borderRadius: CIRCLE_SIZE / 2, + position: 'absolute', + left: 0, + top: 0, + }, + container: { + flex: 1, + paddingTop: 64, + }, +}); + +module.exports = PanResponderExample;
\ No newline at end of file diff --git a/releases/0.37/docs/performance.html b/releases/0.37/docs/performance.html new file mode 100644 index 00000000000..a2dcc6a5baa --- /dev/null +++ b/releases/0.37/docs/performance.html @@ -0,0 +1,211 @@ +Performance

Performance #

A compelling reason for using React Native instead of WebView-based +tools is to achieve 60 FPS and a native look & feel to your apps. Where +possible, we would like for React Native to do the right thing and help +you to focus on your app instead of performance optimization, but there +are areas where we're not quite there yet, and others where React Native +(similar to writing native code directly) cannot possibly determine the +best way to optimize for you and so manual intervention will be +necessary.

This guide is intended to teach you some basics to help you +to troubleshoot performance issues, as well as discuss common sources of +problems and their suggested solutions.

What you need to know about frames #

Your grandparents' generation called movies "moving +pictures" for a reason: +realistic motion in video is an illusion created by quickly changing +static images at a consistent speed. We refer to each of these images as +frames. The number of frames that is displayed each second has a direct +impact on how smooth and ultimately life-like a video (or user +interface) seems to be. iOS devices display 60 frames per second, which +gives you and the UI system about 16.67ms to do all of the work needed to +generate the static image (frame) that the user will see on the screen +for that interval. If you are unable to do the work necessary to +generate that frame within the allotted 16.67ms, then you will "drop a +frame" and the UI will appear unresponsive.

Now to confuse the matter a little bit, open up the developer menu in +your app and toggle Show Perf Monitor. You will notice that there are +two different frame rates.

JavaScript frame rate #

For most React Native applications, your business logic will run on the +JavaScript thread. This is where your React application lives, API calls +are made, touch events are processed, etc... Updates to native-backed +views are batched and sent over to the native side at the end of each iteration of the event loop, before the frame deadline (if +all goes well). If the JavaScript thread is unresponsive for a frame, it +will be considered a dropped frame. For example, if you were to call +this.setState on the root component of a complex application and it +resulted in re-rendering computationally expensive component subtrees, +it's conceivable that this might take 200ms and result in 12 frames +being dropped. Any animations controlled by JavaScript would appear to freeze during that time. If anything takes longer than 100ms, the user will feel it.

This often happens during Navigator transitions: when you push a new +route, the JavaScript thread needs to render all of the components +necessary for the scene in order to send over the proper commands to the +native side to create the backing views. It's common for the work being +done here to take a few frames and cause jank because the transition is +controlled by the JavaScript thread. Sometimes components will do +additional work on componentDidMount, which might result in a second +stutter in the transition.

Another example is responding to touches: if you are doing work across +multiple frames on the JavaScript thread, you might notice a delay in +responding to TouchableOpacity, for example. This is because the JavaScript thread is busy and cannot process the raw touch events sent over from the main thread. As a result, TouchableOpacity cannot react to the touch events and command the native view to adjust its opacity.

Main thread (aka UI thread) frame rate #

Many people have noticed that performance of NavigatorIOS is better +out of the box than Navigator. The reason for this is that the +animations for the transitions are done entirely on the main thread, and +so they are not interrupted by frame drops on the JavaScript thread. +(Read about why you should probably use Navigator +anyways.

Similarly, you can happily scroll up and down through a ScrollView when +the JavaScript thread is locked up because the ScrollView lives on the +main thread (the scroll events are dispatched to the JS thread though, +but their receipt is not necessary for the scroll to occur).

Common sources of performance problems #

Console.log statements #

When running a bundled app, these statements can cause a big bottleneck in the JavaScript thread. This includes calls from debugging libraries such as redux-logger, so make sure to remove them before bundling.

There is a babel plugin that can remove all console.* calls. You need to install it first using npm install babel-plugin-transform-remove-console --save, and then edit (or create) .babelrc under your project directory like the following:

{ + "env": { + "production": { + "plugins": ["transform-remove-console"] + } + } +}

Then it will automatically remove all console.* calls in a release (production) version of your project. However, the console.* calls will still be executed in the debug version of your project.

Development mode (dev=true) #

JavaScript thread performance suffers greatly when running in dev mode. +This is unavoidable: a lot more work needs to be done at runtime to +provide you with good warnings and error messages, such as validating +propTypes and various other assertions.

Slow navigator transitions #

As mentioned above, Navigator animations are controlled by the +JavaScript thread. Imagine the "push from right" scene transition: each +frame, the new scene is moved from the right to left, starting offscreen +(let's say at an x-offset of 320) and ultimately settling when the scene sits +at an x-offset of 0. Each frame during this transition, the +JavaScript thread needs to send a new x-offset to the main thread. +If the JavaScript thread is locked up, it cannot do this and so no +update occurs on that frame and the animation stutters.

Part of the long-term solution to this is to allow for JavaScript-based +animations to be offloaded to the main thread. If we were to do the same +thing as in the above example with this approach, we might calculate a +list of all x-offsets for the new scene when we are starting the +transition and send them to the main thread to execute in an +optimized way. Now that the JavaScript thread is freed of this +responsibility, it's not a big deal if it drops a few frames while +rendering the scene -- you probably won't even notice because you will be +too distracted by the pretty transition.

Unfortunately this solution is not yet implemented, and so in the +meantime we should use the InteractionManager to selectively render the +minimal amount of content necessary for the new scene as long as the +animation is in progress. InteractionManager.runAfterInteractions takes +a callback as its only argument, and that callback is fired when the +navigator transition is complete (each animation from the Animated API +also notifies the InteractionManager, but that's beyond the scope of +this discussion).

Your scene component might look something like this:

class ExpensiveScene extends React.Component { + constructor(props, context) { + super(props, context); + this.state = {renderPlaceholderOnly: true}; + } + + componentDidMount() { + InteractionManager.runAfterInteractions(() => { + this.setState({renderPlaceholderOnly: false}); + }); + } + + render() { + if (this.state.renderPlaceholderOnly) { + return this._renderPlaceholderView(); + } + + return ( + <View> + <Text>Your full view goes here</Text> + </View> + ); + } + + + _renderPlaceholderView() { + return ( + <View> + <Text>Loading...</Text> + </View> + ); + } +};

You don't need to be limited to rendering some loading indicator, you +could alternatively render part of your content -- for example, when you +load the Facebook app you see a placeholder news feed item with grey +rectangles where text will be. If you are rendering a Map in your new +scene, you might want to display a grey placeholder view or a spinner +until the transition is complete as this can actually cause frames to be +dropped on the main thread.

ListView initial rendering is too slow or scroll performance is bad for large lists #

This is an issue that comes up frequently because iOS ships with +UITableView which gives you very good performance by re-using underlying +UIViews. Work is in progress to do something similar with React Native, +but until then we have some tools at our disposal to help us tweak the +performance to suit our needs. It may not be possible to get all the way +there, but a little bit of creativity and experimentation with these +options can go a long way.

initialListSize #

This prop specifies how many rows we want to render on our first render +pass. If we are concerned with getting something on screen as quickly +as possible, we could set the initialListSize to 1, and we'll quickly +see other rows fill in on subsequent frames. The number of rows per +frame is determined by the pageSize.

pageSize #

After the initial render where initialListSize is used, ListView looks +at the pageSize to determine how many rows to render per frame. The +default here is 1 -- but if your views are very small and inexpensive to +render, you might want to bump this up. Tweak it and find what works for +your use case.

scrollRenderAheadDistance #

"How early to start rendering rows before they come on screen, in pixels."

If we had a list with 2000 items and rendered them all immediately that +would be a poor use of both memory and computational resources. It would +also probably cause some pretty awful jank. So the scrollRenderAhead +distance allows us to specify how far beyond the current viewport we +should continue to render rows.

removeClippedSubviews #

"When true, offscreen child views (whose overflow value is hidden) +are removed from their native backing superview when offscreen. This +can improve scrolling performance on long lists. The default value is +true."(The default value is false before version 0.14-rc).

This is an extremely important optimization to apply on large ListViews. +On Android the overflow value is always hidden so you don't need to +worry about setting it, but on iOS you need to be sure to set overflow: +hidden on row containers.

My component renders too slowly and I don't need it all immediately #

It's common at first to overlook ListView, but using it properly is +often key to achieving solid performance. As discussed above, it +provides you with a set of tools that lets you split rendering of your +view across various frames and tweak that behavior to fit your specific +needs. Remember that ListView can be horizontal too.

JS FPS plunges when re-rendering a view that hardly changes #

If you are using a ListView, you must provide a rowHasChanged function +that can reduce a lot of work by quickly determining whether or not a +row needs to be re-rendered. If you are using immutable data structures, +this would be as simple as a reference equality check.

Similarly, you can implement shouldComponentUpdate and indicate the +exact conditions under which you would like the component to re-render. +If you write pure components (where the return value of the render +function is entirely dependent on props and state), you can leverage +PureRenderMixin to do this for you. Once again, immutable data +structures are useful to keep this fast -- if you have to do a deep +comparison of a large list of objects, it may be that re-rendering your +entire component would be quicker, and it would certainly require less +code.

Dropping JS thread FPS because of doing a lot of work on the JavaScript thread at the same time #

"Slow Navigator transitions" is the most common manifestation of this, +but there are other times this can happen. Using InteractionManager can +be a good approach, but if the user experience cost is too high to delay +work during an animation, then you might want to consider +LayoutAnimation.

The Animated api currently calculates each keyframe on-demand on the +JavaScript thread, while LayoutAnimation leverages Core Animation and is +unaffected by JS thread and main thread frame drops.

One case where I have used this is for animating in a modal (sliding +down from top and fading in a translucent overlay) while +initializing and perhaps receiving responses for several network +requests, rendering the contents of the modal, and updating the view +where the modal was opened from. See the Animations guide for more +information about how to use LayoutAnimation.

Caveats: +- LayoutAnimation only works for fire-and-forget animations ("static" + animations) -- if it must be be interruptible, you will need to use +Animated.

Moving a view on the screen (scrolling, translating, rotating) drops UI thread FPS #

This is especially true when you have text with a transparent background +positioned on top of an image, or any other situation where alpha +compositing would be required to re-draw the view on each frame. You +will find that enabling shouldRasterizeIOS or renderToHardwareTextureAndroid +can help with this significantly.

Be careful not to overuse this or your memory usage could go through the +roof. Profile your performance and memory usage when using these props. If you don't plan to move a view anymore, turn this property off.

Animating the size of an image drops UI thread FPS #

On iOS, each time you adjust the width or height of an Image component +it is re-cropped and scaled from the original image. This can be very expensive, +especially for large images. Instead, use the transform: [{scale}] +style property to animate the size. An example of when you might do this is +when you tap an image and zoom it in to full screen.

My TouchableX view isn't very responsive #

Sometimes, if we do an action in the same frame that we are adjusting +the opacity or highlight of a component that is responding to a touch, +we won't see that effect until after the onPress function has returned. +If onPress does a setState that results in a lot of work and a few +frames dropped, this may occur. A solution to this is to wrap any action +inside of your onPress handler in requestAnimationFrame:

handleOnPress() { + // Always use TimerMixin with requestAnimationFrame, setTimeout and + // setInterval + this.requestAnimationFrame(() => { + this.doExpensiveAction(); + }); +}

Profiling #

Use the built-in Profiler to get detailed information about work done in +the JavaScript thread and main thread side-by-side.

For iOS, Instruments are an invaluable tool, and on Android you should +learn to use systrace.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/permissionsandroid.html b/releases/0.37/docs/permissionsandroid.html new file mode 100644 index 00000000000..096b47efa2e --- /dev/null +++ b/releases/0.37/docs/permissionsandroid.html @@ -0,0 +1,52 @@ +PermissionsAndroid

PermissionsAndroid #

PermissionsAndroid provides access to Android M's new permissions model. +Some permissions are granted by default when the application is installed +so long as they appear in AndroidManifest.xml. However, "dangerous" +permissions require a dialog prompt. You should use this module for those +permissions.

On devices before SDK version 23, the permissions are automatically granted +if they appear in the manifest, so checkPermission and requestPermission +should always be true.

If a user has previously turned off a permission that you prompt for, the OS +will advise your app to show a rationale for needing the permission. The +optional rationale argument will show a dialog prompt only if +necessary - otherwise the normal permission prompt will appear.

Example #

async function requestCameraPermission() { + try { + const granted = await PermissionsAndroid.requestPermission( + PermissionsAndroid.PERMISSIONS.CAMERA, + { + 'title': 'Cool Photo App Camera Permission', + 'message': 'Cool Photo App needs access to your camera ' + + 'so you can take awesome pictures.' + } + ) + if (granted) { + console.log("You can use the camera") + } else { + console.log("Camera permission denied") + } + } catch (err) { + console.warn(err) + } +}

Methods #

constructor(0) #

checkPermission(permission) #

Returns a promise resolving to a boolean value as to whether the specified +permissions has been granted

requestPermission(permission, rationale?) #

Prompts the user to enable a permission and returns a promise resolving to a +boolean value indicating whether the user allowed or denied the request

If the optional rationale argument is included (which is an object with a +title and message), this function checks with the OS whether it is +necessary to show a dialog explaining why the permission is needed +(https://developer.android.com/training/permissions/requesting.html#explain) +and then shows the system permission dialog

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/picker.html b/releases/0.37/docs/picker.html new file mode 100644 index 00000000000..e5727e745e7 --- /dev/null +++ b/releases/0.37/docs/picker.html @@ -0,0 +1,145 @@ +Picker

Picker #

Renders the native picker component on iOS and Android. Example:

<Picker + selectedValue={this.state.language} + onValueChange={(lang) => this.setState({language: lang})}> + <Picker.Item label="Java" value="java" /> + <Picker.Item label="JavaScript" value="js" /> +</Picker>

Props #

onValueChange function #

Callback for when an item is selected. This is called with the following parameters: + - itemValue: the value prop of the item that was selected + - itemPosition: the index of the selected item in this picker

selectedValue any #

Value matching value of one of the items. Can be a string or an integer.

style pickerStyleType #

testID string #

Used to locate this view in end-to-end tests.

androidenabled bool #

If set to false, the picker will be disabled, i.e. the user will not be able to make a +selection.

androidmode enum('dialog', 'dropdown') #

On Android, specifies how to display the selection items when the user taps on the picker:

  • 'dialog': Show a modal dialog. This is the default.
  • 'dropdown': Shows a dropdown anchored to the picker view

androidprompt string #

Prompt string for this picker, used on Android in dialog mode as the title of the dialog.

iositemStyle itemStylePropType #

Style to apply to each of the item labels.

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +const React = require('react'); +const ReactNative = require('react-native'); +const StyleSheet = require('StyleSheet'); +const UIExplorerBlock = require('UIExplorerBlock'); +const UIExplorerPage = require('UIExplorerPage'); + +const { + Picker, + Text, +} = ReactNative; + +const Item = Picker.Item; + +class PickerExample extends React.Component { + static title = '<Picker>'; + static description = 'Provides multiple options to choose from, using either a dropdown menu or a dialog.'; + + state = { + selected1: 'key1', + selected2: 'key1', + selected3: 'key1', + color: 'red', + mode: Picker.MODE_DIALOG, + }; + + render() { + return ( + <UIExplorerPage title="<Picker>"> + <UIExplorerBlock title="Basic Picker"> + <Picker + style={styles.picker} + selectedValue={this.state.selected1} + onValueChange={this.onValueChange.bind(this, 'selected1')}> + <Item label="hello" value="key0" /> + <Item label="world" value="key1" /> + </Picker> + </UIExplorerBlock> + <UIExplorerBlock title="Disabled picker"> + <Picker style={styles.picker} enabled={false} selectedValue={this.state.selected1}> + <Item label="hello" value="key0" /> + <Item label="world" value="key1" /> + </Picker> + </UIExplorerBlock> + <UIExplorerBlock title="Dropdown Picker"> + <Picker + style={styles.picker} + selectedValue={this.state.selected2} + onValueChange={this.onValueChange.bind(this, 'selected2')} + mode="dropdown"> + <Item label="hello" value="key0" /> + <Item label="world" value="key1" /> + </Picker> + </UIExplorerBlock> + <UIExplorerBlock title="Picker with prompt message"> + <Picker + style={styles.picker} + selectedValue={this.state.selected3} + onValueChange={this.onValueChange.bind(this, 'selected3')} + prompt="Pick one, just one"> + <Item label="hello" value="key0" /> + <Item label="world" value="key1" /> + </Picker> + </UIExplorerBlock> + <UIExplorerBlock title="Picker with no listener"> + <Picker style={styles.picker}> + <Item label="hello" value="key0" /> + <Item label="world" value="key1" /> + </Picker> + <Text> + Cannot change the value of this picker because it doesn't update selectedValue. + </Text> + </UIExplorerBlock> + <UIExplorerBlock title="Colorful pickers"> + <Picker + style={[styles.picker, {color: 'white', backgroundColor: '#333'}]} + selectedValue={this.state.color} + onValueChange={this.onValueChange.bind(this, 'color')} + mode="dropdown"> + <Item label="red" color="red" value="red" /> + <Item label="green" color="green" value="green" /> + <Item label="blue" color="blue" value="blue" /> + </Picker> + <Picker + style={styles.picker} + selectedValue={this.state.color} + onValueChange={this.onValueChange.bind(this, 'color')} + mode="dialog"> + <Item label="red" color="red" value="red" /> + <Item label="green" color="green" value="green" /> + <Item label="blue" color="blue" value="blue" /> + </Picker> + </UIExplorerBlock> + </UIExplorerPage> + ); + } + + changeMode = () => { + const newMode = this.state.mode === Picker.MODE_DIALOG + ? Picker.MODE_DROPDOWN + : Picker.MODE_DIALOG; + this.setState({mode: newMode}); + }; + + onValueChange = (key: string, value: string) => { + const newState = {}; + newState[key] = value; + this.setState(newState); + }; +} + +var styles = StyleSheet.create({ + picker: { + width: 100, + }, +}); + +module.exports = PickerExample;
\ No newline at end of file diff --git a/releases/0.37/docs/pickerios.html b/releases/0.37/docs/pickerios.html new file mode 100644 index 00000000000..fb50f623e3c --- /dev/null +++ b/releases/0.37/docs/pickerios.html @@ -0,0 +1,153 @@ +PickerIOS

PickerIOS #

Props #

itemStyle itemStylePropType #

onValueChange function #

selectedValue any #

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + PickerIOS, + Text, + View, +} = ReactNative; + +var PickerItemIOS = PickerIOS.Item; + +var CAR_MAKES_AND_MODELS = { + amc: { + name: 'AMC', + models: ['AMX', 'Concord', 'Eagle', 'Gremlin', 'Matador', 'Pacer'], + }, + alfa: { + name: 'Alfa-Romeo', + models: ['159', '4C', 'Alfasud', 'Brera', 'GTV6', 'Giulia', 'MiTo', 'Spider'], + }, + aston: { + name: 'Aston Martin', + models: ['DB5', 'DB9', 'DBS', 'Rapide', 'Vanquish', 'Vantage'], + }, + audi: { + name: 'Audi', + models: ['90', '4000', '5000', 'A3', 'A4', 'A5', 'A6', 'A7', 'A8', 'Q5', 'Q7'], + }, + austin: { + name: 'Austin', + models: ['America', 'Maestro', 'Maxi', 'Mini', 'Montego', 'Princess'], + }, + borgward: { + name: 'Borgward', + models: ['Hansa', 'Isabella', 'P100'], + }, + buick: { + name: 'Buick', + models: ['Electra', 'LaCrosse', 'LeSabre', 'Park Avenue', 'Regal', + 'Roadmaster', 'Skylark'], + }, + cadillac: { + name: 'Cadillac', + models: ['Catera', 'Cimarron', 'Eldorado', 'Fleetwood', 'Sedan de Ville'], + }, + chevrolet: { + name: 'Chevrolet', + models: ['Astro', 'Aveo', 'Bel Air', 'Captiva', 'Cavalier', 'Chevelle', + 'Corvair', 'Corvette', 'Cruze', 'Nova', 'SS', 'Vega', 'Volt'], + }, +}; + +class PickerExample extends React.Component { + state = { + carMake: 'cadillac', + modelIndex: 3, + }; + + render() { + var make = CAR_MAKES_AND_MODELS[this.state.carMake]; + var selectionString = make.name + ' ' + make.models[this.state.modelIndex]; + return ( + <View> + <Text>Please choose a make for your car:</Text> + <PickerIOS + selectedValue={this.state.carMake} + onValueChange={(carMake) => this.setState({carMake, modelIndex: 0})}> + {Object.keys(CAR_MAKES_AND_MODELS).map((carMake) => ( + <PickerItemIOS + key={carMake} + value={carMake} + label={CAR_MAKES_AND_MODELS[carMake].name} + /> + ))} + </PickerIOS> + <Text>Please choose a model of {make.name}:</Text> + <PickerIOS + selectedValue={this.state.modelIndex} + key={this.state.carMake} + onValueChange={(modelIndex) => this.setState({modelIndex})}> + {CAR_MAKES_AND_MODELS[this.state.carMake].models.map((modelName, modelIndex) => ( + <PickerItemIOS + key={this.state.carMake + '_' + modelIndex} + value={modelIndex} + label={modelName} + /> + ))} + </PickerIOS> + <Text>You selected: {selectionString}</Text> + </View> + ); + } +} + +class PickerStyleExample extends React.Component { + state = { + carMake: 'cadillac', + modelIndex: 0, + }; + + render() { + return ( + <PickerIOS + itemStyle={{fontSize: 25, color: 'red', textAlign: 'left', fontWeight: 'bold'}} + selectedValue={this.state.carMake} + onValueChange={(carMake) => this.setState({carMake, modelIndex: 0})}> + {Object.keys(CAR_MAKES_AND_MODELS).map((carMake) => ( + <PickerItemIOS + key={carMake} + value={carMake} + label={CAR_MAKES_AND_MODELS[carMake].name} + /> + ))} + </PickerIOS> + ); + } +} + +exports.displayName = (undefined: ?string); +exports.title = '<PickerIOS>'; +exports.description = 'Render lists of selectable options with UIPickerView.'; +exports.examples = [ +{ + title: '<PickerIOS>', + render: function(): React.Element<any> { + return <PickerExample />; + }, +}, +{ + title: '<PickerIOS> with custom styling', + render: function(): React.Element<any> { + return <PickerStyleExample />; + }, +}];
\ No newline at end of file diff --git a/releases/0.37/docs/pixelratio.html b/releases/0.37/docs/pixelratio.html new file mode 100644 index 00000000000..68ac10440f5 --- /dev/null +++ b/releases/0.37/docs/pixelratio.html @@ -0,0 +1,32 @@ +PixelRatio

PixelRatio #

PixelRatio class gives access to the device pixel density.

Fetching a correctly sized image #

You should get a higher resolution image if you are on a high pixel density +device. A good rule of thumb is to multiply the size of the image you display +by the pixel ratio.

var image = getImage({ + width: PixelRatio.getPixelSizeForLayoutSize(200), + height: PixelRatio.getPixelSizeForLayoutSize(100), +}); +<Image source={image} style={{width: 200, height: 100}} />

Methods #

static get(0) #

Returns the device pixel density. Some examples:

  • PixelRatio.get() === 1
    • mdpi Android devices (160 dpi)
  • PixelRatio.get() === 1.5
    • hdpi Android devices (240 dpi)
  • PixelRatio.get() === 2
    • iPhone 4, 4S
    • iPhone 5, 5c, 5s
    • iPhone 6
    • xhdpi Android devices (320 dpi)
  • PixelRatio.get() === 3
    • iPhone 6 plus
    • xxhdpi Android devices (480 dpi)
  • PixelRatio.get() === 3.5
    • Nexus 6

static getFontScale(0) #

Returns the scaling factor for font sizes. This is the ratio that is used to calculate the +absolute font size, so any elements that heavily depend on that should use this to do +calculations.

If a font scale is not set, this returns the device pixel ratio.

Currently this is only implemented on Android and reflects the user preference set in +Settings > Display > Font size, on iOS it will always return the default pixel ratio. +@platform android

static getPixelSizeForLayoutSize(layoutSize) #

Converts a layout size (dp) to pixel size (px).

Guaranteed to return an integer number.

static roundToNearestPixel(layoutSize) #

Rounds a layout size (dp) to the nearest layout size that corresponds to +an integer number of pixels. For example, on a device with a PixelRatio +of 3, PixelRatio.roundToNearestPixel(8.4) = 8.33, which corresponds to +exactly (8.33 * 3) = 25 pixels.

static startDetecting(0) #

// No-op for iOS, but used on the web. Should not be documented.

You can edit the content above on GitHub and send us a pull request!

Description #

Pixel Grid Snapping #

In iOS, you can specify positions and dimensions for elements with arbitrary precision, for example 29.674825. But, ultimately the physical display only have a fixed number of pixels, for example 640×960 for iphone 4 or 750×1334 for iphone 6. iOS tries to be as faithful as possible to the user value by spreading one original pixel into multiple ones to trick the eye. The downside of this technique is that it makes the resulting element look blurry.

In practice, we found out that developers do not want this feature and they have to work around it by doing manual rounding in order to avoid having blurry elements. In React Native, we are rounding all the pixels automatically.

We have to be careful when to do this rounding. You never want to work with rounded and unrounded values at the same time as you're going to accumulate rounding errors. Having even one rounding error is deadly because a one pixel border may vanish or be twice as big.

In React Native, everything in JS and within the layout engine work with arbitrary precision numbers. It's only when we set the position and dimensions of the native element on the main thread that we round. Also, rounding is done relative to the root rather than the parent, again to avoid accumulating rounding errors.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/platform-specific-code.html b/releases/0.37/docs/platform-specific-code.html new file mode 100644 index 00000000000..ac024544a37 --- /dev/null +++ b/releases/0.37/docs/platform-specific-code.html @@ -0,0 +1,47 @@ +Platform Specific Code

Platform Specific Code #

When building a cross-platform app, you'll want to re-use as much code as possible. Scenarios may arise where it makes sense for the code to be different, for example you may want to implement separate visual components for iOS and Android.

React Native provides two ways to easily organize your code and separate it by platform:

Certain components may have properties that work on one platform only. All of these props are annotated with @platform and have a small badge next to them on the website.

Platform module #

React Native provides a module that detects the platform in which the app is running. You can use the detection logic to implement platform-specific code. Use this option when only small parts of a component are platform-specific.

import { Platform, StyleSheet } from 'react-native'; + +const styles = StyleSheet.create({ + height: (Platform.OS === 'ios') ? 200 : 100, +});

Platform.OS will be ios when running on iOS and android when running on Android.

There is also a Platform.select method available, that given an object containing Platform.OS as keys, returns the value for the platform you are currently running on.

import { Platform, StyleSheet } from 'react-native'; + +const styles = StyleSheet.create({ + container: { + flex: 1, + ...Platform.select({ + ios: { + backgroundColor: 'red', + }, + android: { + backgroundColor: 'blue', + }, + }), + }, +});

This will result in a container having flex: 1 on both platforms, a red background color on iOS, and a blue background color on Android.

Since it accepts any value, you can also use it to return platform specific component, like below:

const Component = Platform.select({ + ios: () => require('ComponentIOS'), + android: () => require('ComponentAndroid'), +})(); + +<Component />;

Detecting the Android version #

On Android, the Platform module can also be used to detect the version of the Android Platform in which the app is running:

import { Platform } from 'react-native'; + +if(Platform.Version === 21){ + console.log('Running on Lollipop!'); +}

Platform-specific extensions #

When your platform-specific code is more complex, you should consider splitting the code out into separate files. React Native will detect when a file has a .ios. or .android. extension and load the relevant platform file when required from other components.

For example, say you have the following files in your project:

BigButton.ios.js +BigButton.android.js

You can then require the component as follows:

const BigButton = require('./BigButton');

React Native will automatically pick up the right file based on the running platform.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/progressbarandroid.html b/releases/0.37/docs/progressbarandroid.html new file mode 100644 index 00000000000..a233ae156d7 --- /dev/null +++ b/releases/0.37/docs/progressbarandroid.html @@ -0,0 +1,93 @@ +ProgressBarAndroid

ProgressBarAndroid #

React component that wraps the Android-only ProgressBar. This component is used to indicate +that the app is loading or there is some activity in the app.

Example:

render: function() { + var progressBar = + <View style={styles.container}> + <ProgressBar styleAttr="Inverse" /> + </View>; + + return ( + <MyLoadingComponent + componentView={componentView} + loadingView={progressBar} + style={styles.loadingComponent} + /> + ); +},

Props #

color color #

Color of the progress bar.

indeterminate indeterminateType #

If the progress bar will show indeterminate progress. Note that this +can only be false if styleAttr is Horizontal.

progress number #

The progress value (between 0 and 1).

styleAttr enum('Horizontal', 'Normal', 'Small', 'Large', 'Inverse', 'SmallInverse', 'LargeInverse') #

Style of the ProgressBar. One of:

  • Horizontal
  • Normal (default)
  • Small
  • Large
  • Inverse
  • SmallInverse
  • LargeInverse

testID string #

Used to locate this view in end-to-end tests.

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +var ProgressBar = require('ProgressBarAndroid'); +var React = require('React'); +var UIExplorerBlock = require('UIExplorerBlock'); +var UIExplorerPage = require('UIExplorerPage'); + +var TimerMixin = require('react-timer-mixin'); + +var MovingBar = React.createClass({ + mixins: [TimerMixin], + + getInitialState: function() { + return { + progress: 0 + }; + }, + + componentDidMount: function() { + this.setInterval( + () => { + var progress = (this.state.progress + 0.02) % 1; + this.setState({progress: progress}); + }, 50 + ); + }, + + render: function() { + return <ProgressBar progress={this.state.progress} {...this.props} />; + }, +}); + +class ProgressBarAndroidExample extends React.Component { + static title = '<ProgressBarAndroid>'; + static description = 'Horizontal bar to show the progress of some operation.'; + + render() { + return ( + <UIExplorerPage title="ProgressBar Examples"> + <UIExplorerBlock title="Horizontal Indeterminate ProgressBar"> + <ProgressBar styleAttr="Horizontal" /> + </UIExplorerBlock> + + <UIExplorerBlock title="Horizontal ProgressBar"> + <MovingBar styleAttr="Horizontal" indeterminate={false} /> + </UIExplorerBlock> + + <UIExplorerBlock title="Horizontal Black Indeterminate ProgressBar"> + <ProgressBar styleAttr="Horizontal" color="black" /> + </UIExplorerBlock> + + <UIExplorerBlock title="Horizontal Blue ProgressBar"> + <MovingBar styleAttr="Horizontal" indeterminate={false} color="blue" /> + </UIExplorerBlock> + </UIExplorerPage> + ); + } +} + +module.exports = ProgressBarAndroidExample;
\ No newline at end of file diff --git a/releases/0.37/docs/progressviewios.html b/releases/0.37/docs/progressviewios.html new file mode 100644 index 00000000000..801e1e77de1 --- /dev/null +++ b/releases/0.37/docs/progressviewios.html @@ -0,0 +1,88 @@ +ProgressViewIOS

ProgressViewIOS #

Use ProgressViewIOS to render a UIProgressView on iOS.

Props #

progress number #

The progress value (between 0 and 1).

progressImage Image.propTypes.source #

A stretchable image to display as the progress bar.

progressTintColor string #

The tint color of the progress bar itself.

progressViewStyle enum('default', 'bar') #

The progress bar style.

trackImage Image.propTypes.source #

A stretchable image to display behind the progress bar.

trackTintColor string #

The tint color of the progress bar track.

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + ProgressViewIOS, + StyleSheet, + View, +} = ReactNative; +var TimerMixin = require('react-timer-mixin'); + +var ProgressViewExample = React.createClass({ + mixins: [TimerMixin], + + getInitialState() { + return { + progress: 0, + }; + }, + + componentDidMount() { + this.updateProgress(); + }, + + updateProgress() { + var progress = this.state.progress + 0.01; + this.setState({ progress }); + this.requestAnimationFrame(() => this.updateProgress()); + }, + + getProgress(offset) { + var progress = this.state.progress + offset; + return Math.sin(progress % Math.PI) % 1; + }, + + render() { + return ( + <View style={styles.container}> + <ProgressViewIOS style={styles.progressView} progress={this.getProgress(0)}/> + <ProgressViewIOS style={styles.progressView} progressTintColor="purple" progress={this.getProgress(0.2)}/> + <ProgressViewIOS style={styles.progressView} progressTintColor="red" progress={this.getProgress(0.4)}/> + <ProgressViewIOS style={styles.progressView} progressTintColor="orange" progress={this.getProgress(0.6)}/> + <ProgressViewIOS style={styles.progressView} progressTintColor="yellow" progress={this.getProgress(0.8)}/> + </View> + ); + }, +}); + +exports.displayName = (undefined: ?string); +exports.framework = 'React'; +exports.title = 'ProgressViewIOS'; +exports.description = 'ProgressViewIOS'; +exports.examples = [{ + title: 'ProgressViewIOS', + render() { + return ( + <ProgressViewExample/> + ); + } +}]; + +var styles = StyleSheet.create({ + container: { + marginTop: -20, + backgroundColor: 'transparent', + }, + progressView: { + marginTop: 20, + } +});
\ No newline at end of file diff --git a/releases/0.37/docs/props.html b/releases/0.37/docs/props.html new file mode 100644 index 00000000000..32203575864 --- /dev/null +++ b/releases/0.37/docs/props.html @@ -0,0 +1,61 @@ +Props

Props #

Most components can be customized when they are created, with different parameters. These creation parameters are called props.

For example, one basic React Native component is the Image. When you +create an image, you can use a prop named source to control what image it shows.

import React, { Component } from 'react'; +import { AppRegistry, Image } from 'react-native'; + +class Bananas extends Component { + render() { + let pic = { + uri: 'https://upload.wikimedia.org/wikipedia/commons/d/de/Bananavarieties.jpg' + }; + return ( + <Image source={pic} style={{width: 193, height: 110}}/> + ); + } +} + +AppRegistry.registerComponent('Bananas', () => Bananas);

Notice that {pic} is surrounded by braces, to embed the variable pic into JSX. You can put any JavaScript expression inside braces in JSX.

Your own components can also use props. This lets you make a single component +that is used in many different places in your app, with slightly different +properties in each place. Just refer to this.props in your render function. Here's an example:

import React, { Component } from 'react'; +import { AppRegistry, Text, View } from 'react-native'; + +class Greeting extends Component { + render() { + return ( + <Text>Hello {this.props.name}!</Text> + ); + } +} + +class LotsOfGreetings extends Component { + render() { + return ( + <View style={{alignItems: 'center'}}> + <Greeting name='Rexxar' /> + <Greeting name='Jaina' /> + <Greeting name='Valeera' /> + </View> + ); + } +} + +AppRegistry.registerComponent('LotsOfGreetings', () => LotsOfGreetings);

Using name as a prop lets us customize the Greeting component, so we can reuse that component for each of our greetings. This example also uses the Greeting component in JSX, just like the built-in components. The power to do this is what makes React so cool - if you find yourself wishing that you had a different set of UI primitives to work with, you just invent new ones.

The other new thing going on here is the View component. A View is useful +as a container for other components, to help control style and layout.

With props and the basic Text, Image, and View components, you can +build a wide variety of static screens. To learn how to make your app change over time, you need to learn about State.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/pushnotificationios.html b/releases/0.37/docs/pushnotificationios.html new file mode 100644 index 00000000000..c1dda8d8ff2 --- /dev/null +++ b/releases/0.37/docs/pushnotificationios.html @@ -0,0 +1,269 @@ +PushNotificationIOS

PushNotificationIOS #

Handle push notifications for your app, including permission handling and +icon badge number.

To get up and running, configure your notifications with Apple +and your server-side system. To get an idea, this is the Parse guide.

Manually link the PushNotificationIOS library

  • Add the following to your Project: node_modules/react-native/Libraries/PushNotificationIOS/RCTPushNotification.xcodeproj
  • Add the following to Link Binary With Libraries: libRCTPushNotification.a
  • Add the following to your Header Search Paths: +$(SRCROOT)/../node_modules/react-native/Libraries/PushNotificationIOS and set the search to recursive

Finally, to enable support for notification and register events you need to augment your AppDelegate.

At the top of your AppDelegate.m:

#import "RCTPushNotificationManager.h"

And then in your AppDelegate implementation add the following:

// Required to register for notifications + - (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings + { + [RCTPushNotificationManager didRegisterUserNotificationSettings:notificationSettings]; + } + // Required for the register event. + - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken + { + [RCTPushNotificationManager didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; + } + // Required for the registrationError event. + - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error + { + [RCTPushNotificationManager didFailToRegisterForRemoteNotificationsWithError:error]; + } + // Required for the notification event. + - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)notification + { + [RCTPushNotificationManager didReceiveRemoteNotification:notification]; + } + // Required for the localNotification event. + - (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification + { + [RCTPushNotificationManager didReceiveLocalNotification:notification]; + }

Methods #

static presentLocalNotification(details) #

Schedules the localNotification for immediate presentation.

details is an object containing:

  • alertBody : The message displayed in the notification alert.
  • alertAction : The "action" displayed beneath an actionable notification. Defaults to "view";
  • soundName : The sound played when the notification is fired (optional).
  • category : The category of this notification, required for actionable notifications (optional).
  • userInfo : An optional object containing additional notification data.
  • applicationIconBadgeNumber (optional) : The number to display as the app's icon badge. The default value of this property is 0, which means that no badge is displayed.

static scheduleLocalNotification(details) #

Schedules the localNotification for future presentation.

details is an object containing:

  • fireDate : The date and time when the system should deliver the notification.
  • alertBody : The message displayed in the notification alert.
  • alertAction : The "action" displayed beneath an actionable notification. Defaults to "view";
  • soundName : The sound played when the notification is fired (optional).
  • category : The category of this notification, required for actionable notifications (optional).
  • userInfo : An optional object containing additional notification data.
  • applicationIconBadgeNumber (optional) : The number to display as the app's icon badge. Setting the number to 0 removes the icon badge.
  • repeatInterval : The interval to repeat as a string. Possible values: minute, hour, day, week, month, year.

static cancelAllLocalNotifications(0) #

Cancels all scheduled localNotifications

static setApplicationIconBadgeNumber(number) #

Sets the badge number for the app icon on the home screen

static getApplicationIconBadgeNumber(callback) #

Gets the current badge number for the app icon on the home screen

static cancelLocalNotifications(userInfo) #

Cancel local notifications.

Optionally restricts the set of canceled notifications to those +notifications whose userInfo fields match the corresponding fields +in the userInfo argument.

static getScheduledLocalNotifications(callback) #

Gets the local notifications that are currently scheduled.

static addEventListener(type, handler) #

Attaches a listener to remote or local notification events while the app is running +in the foreground or the background.

Valid events are:

  • notification : Fired when a remote notification is received. The +handler will be invoked with an instance of PushNotificationIOS.
  • localNotification : Fired when a local notification is received. The +handler will be invoked with an instance of PushNotificationIOS.
  • register: Fired when the user registers for remote notifications. The +handler will be invoked with a hex string representing the deviceToken.
  • registrationError: Fired when the user fails to register for remote +notifications. Typically occurs when APNS is having issues, or the device +is a simulator. The handler will be invoked with +{message: string, code: number, details: any}.

static removeEventListener(type, handler) #

Removes the event listener. Do this in componentWillUnmount to prevent +memory leaks

static requestPermissions(permissions?) #

Requests notification permissions from iOS, prompting the user's +dialog box. By default, it will request all notification permissions, but +a subset of these can be requested by passing a map of requested +permissions. +The following permissions are supported:

  • alert
  • badge
  • sound

If a map is provided to the method, only the permissions with truthy values +will be requested.

This method returns a promise that will resolve when the user accepts, +rejects, or if the permissions were previously rejected. The promise +resolves to the current state of the permission.

static abandonPermissions(0) #

Unregister for all remote notifications received via Apple Push Notification service.

You should call this method in rare circumstances only, such as when a new version of +the app removes support for all types of remote notifications. Users can temporarily +prevent apps from receiving remote notifications through the Notifications section of +the Settings app. Apps unregistered through this method can always re-register.

static checkPermissions(callback) #

See what push permissions are currently enabled. callback will be +invoked with a permissions object:

  • alert :boolean
  • badge :boolean
  • sound :boolean

static getInitialNotification(0) #

This method returns a promise that resolves to either the notification +object if the app was launched by a push notification, or null otherwise.

constructor(nativeNotif) #

You will never need to instantiate PushNotificationIOS yourself. +Listening to the notification event and invoking +getInitialNotification is sufficient

getMessage(0) #

An alias for getAlert to get the notification's main message string

getSound(0) #

Gets the sound string from the aps object

getAlert(0) #

Gets the notification's main message from the aps object

getBadgeCount(0) #

Gets the badge count number from the aps object

getData(0) #

Gets the data object on the notif

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + AlertIOS, + PushNotificationIOS, + StyleSheet, + Text, + TouchableHighlight, + View, +} = ReactNative; + +class Button extends React.Component { + render() { + return ( + <TouchableHighlight + underlayColor={'white'} + style={styles.button} + onPress={this.props.onPress}> + <Text style={styles.buttonLabel}> + {this.props.label} + </Text> + </TouchableHighlight> + ); + } +} + +class NotificationExample extends React.Component { + componentWillMount() { + PushNotificationIOS.addEventListener('register', this._onRegistered); + PushNotificationIOS.addEventListener('registrationError', this._onRegistrationError); + PushNotificationIOS.addEventListener('notification', this._onRemoteNotification); + PushNotificationIOS.addEventListener('localNotification', this._onLocalNotification); + + PushNotificationIOS.requestPermissions(); + } + + componentWillUnmount() { + PushNotificationIOS.removeEventListener('register', this._onRegistered); + PushNotificationIOS.removeEventListener('registrationError', this._onRegistrationError); + PushNotificationIOS.removeEventListener('notification', this._onRemoteNotification); + PushNotificationIOS.removeEventListener('localNotification', this._onLocalNotification); + } + + render() { + return ( + <View> + <Button + onPress={this._sendNotification} + label="Send fake notification" + /> + + <Button + onPress={this._sendLocalNotification} + label="Send fake local notification" + /> + </View> + ); + } + + _sendNotification() { + require('RCTDeviceEventEmitter').emit('remoteNotificationReceived', { + aps: { + alert: 'Sample notification', + badge: '+1', + sound: 'default', + category: 'REACT_NATIVE' + }, + }); + } + + _sendLocalNotification() { + require('RCTDeviceEventEmitter').emit('localNotificationReceived', { + aps: { + alert: 'Sample local notification', + badge: '+1', + sound: 'default', + category: 'REACT_NATIVE' + }, + }); + } + + _onRegistered(deviceToken) { + AlertIOS.alert( + 'Registered For Remote Push', + `Device Token: ${deviceToken}`, + [{ + text: 'Dismiss', + onPress: null, + }] + ); + } + + _onRegistrationError(error) { + AlertIOS.alert( + 'Failed To Register For Remote Push', + `Error (${error.code}): ${error.message}`, + [{ + text: 'Dismiss', + onPress: null, + }] + ); + } + + _onRemoteNotification(notification) { + AlertIOS.alert( + 'Push Notification Received', + 'Alert message: ' + notification.getMessage(), + [{ + text: 'Dismiss', + onPress: null, + }] + ); + } + + _onLocalNotification(notification){ + AlertIOS.alert( + 'Local Notification Received', + 'Alert message: ' + notification.getMessage(), + [{ + text: 'Dismiss', + onPress: null, + }] + ); + } +} + +class NotificationPermissionExample extends React.Component { + state: any; + + constructor(props) { + super(props); + this.state = {permissions: null}; + } + + render() { + return ( + <View> + <Button + onPress={this._showPermissions.bind(this)} + label="Show enabled permissions" + /> + <Text> + {JSON.stringify(this.state.permissions)} + </Text> + </View> + ); + } + + _showPermissions() { + PushNotificationIOS.checkPermissions((permissions) => { + this.setState({permissions}); + }); + } +} + +var styles = StyleSheet.create({ + button: { + padding: 10, + alignItems: 'center', + justifyContent: 'center', + }, + buttonLabel: { + color: 'blue', + }, +}); + +exports.title = 'PushNotificationIOS'; +exports.description = 'Apple PushNotification and badge value'; +exports.examples = [ +{ + title: 'Badge Number', + render(): React.Element<any> { + return ( + <View> + <Button + onPress={() => PushNotificationIOS.setApplicationIconBadgeNumber(42)} + label="Set app's icon badge to 42" + /> + <Button + onPress={() => PushNotificationIOS.setApplicationIconBadgeNumber(0)} + label="Clear app's icon badge" + /> + </View> + ); + }, +}, +{ + title: 'Push Notifications', + render(): React.Element<any> { + return <NotificationExample />; + } +}, +{ + title: 'Notifications Permissions', + render(): React.Element<any> { + return <NotificationPermissionExample />; + } +}];
\ No newline at end of file diff --git a/releases/0.37/docs/refreshcontrol.html b/releases/0.37/docs/refreshcontrol.html new file mode 100644 index 00000000000..6e455d28425 --- /dev/null +++ b/releases/0.37/docs/refreshcontrol.html @@ -0,0 +1,162 @@ +RefreshControl

RefreshControl #

This component is used inside a ScrollView or ListView to add pull to refresh +functionality. When the ScrollView is at scrollY: 0, swiping down +triggers an onRefresh event.

Usage example #

class RefreshableList extends Component { + constructor(props) { + super(props); + this.state = { + refreshing: false, + }; + } + + _onRefresh() { + this.setState({refreshing: true}); + fetchData().then(() => { + this.setState({refreshing: false}); + }); + } + + render() { + return ( + <ListView + refreshControl={ + <RefreshControl + refreshing={this.state.refreshing} + onRefresh={this._onRefresh.bind(this)} + /> + } + ... + > + ... + </ListView> + ); + } + ... +}

Note: refreshing is a controlled prop, this is why it needs to be set to true +in the onRefresh function otherwise the refresh indicator will stop immediately.

Props #

onRefresh function #

Called when the view starts refreshing.

refreshing bool #

Whether the view should be indicating an active refresh.

androidcolors [color] #

The colors (at least one) that will be used to draw the refresh indicator.

androidenabled bool #

Whether the pull to refresh functionality is enabled.

androidprogressBackgroundColor color #

The background color of the refresh indicator.

androidprogressViewOffset number #

Progress view top offset

androidsize enum(RefreshLayoutConsts.SIZE.DEFAULT, RefreshLayoutConsts.SIZE.LARGE) #

Size of the refresh indicator, see RefreshControl.SIZE.

iostintColor color #

The color of the refresh indicator.

iostitle string #

The title displayed under the refresh indicator.

iostitleColor color #

Title color.

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +const React = require('react'); +const ReactNative = require('react-native'); +const { + ScrollView, + StyleSheet, + RefreshControl, + Text, + TouchableWithoutFeedback, + View, +} = ReactNative; + +const styles = StyleSheet.create({ + row: { + borderColor: 'grey', + borderWidth: 1, + padding: 20, + backgroundColor: '#3a5795', + margin: 5, + }, + text: { + alignSelf: 'center', + color: '#fff', + }, + scrollview: { + flex: 1, + }, +}); + +class Row extends React.Component { + _onClick = () => { + this.props.onClick(this.props.data); + }; + + render() { + return ( + <TouchableWithoutFeedback onPress={this._onClick} > + <View style={styles.row}> + <Text style={styles.text}> + {this.props.data.text + ' (' + this.props.data.clicks + ' clicks)'} + </Text> + </View> + </TouchableWithoutFeedback> + ); + } +} + +class RefreshControlExample extends React.Component { + static title = '<RefreshControl>'; + static description = 'Adds pull-to-refresh support to a scrollview.'; + + state = { + isRefreshing: false, + loaded: 0, + rowData: Array.from(new Array(20)).map( + (val, i) => ({text: 'Initial row ' + i, clicks: 0})), + }; + + _onClick = (row) => { + row.clicks++; + this.setState({ + rowData: this.state.rowData, + }); + }; + + render() { + const rows = this.state.rowData.map((row, ii) => { + return <Row key={ii} data={row} onClick={this._onClick}/>; + }); + return ( + <ScrollView + style={styles.scrollview} + refreshControl={ + <RefreshControl + refreshing={this.state.isRefreshing} + onRefresh={this._onRefresh} + tintColor="#ff0000" + title="Loading..." + titleColor="#00ff00" + colors={['#ff0000', '#00ff00', '#0000ff']} + progressBackgroundColor="#ffff00" + /> + }> + {rows} + </ScrollView> + ); + } + + _onRefresh = () => { + this.setState({isRefreshing: true}); + setTimeout(() => { + // prepend 10 items + const rowData = Array.from(new Array(10)) + .map((val, i) => ({ + text: 'Loaded row ' + (+this.state.loaded + i), + clicks: 0, + })) + .concat(this.state.rowData); + + this.setState({ + loaded: this.state.loaded + 10, + isRefreshing: false, + rowData: rowData, + }); + }, 5000); + }; +} + +module.exports = RefreshControlExample;
\ No newline at end of file diff --git a/releases/0.37/docs/running-on-device-android.html b/releases/0.37/docs/running-on-device-android.html new file mode 100644 index 00000000000..e3847474725 --- /dev/null +++ b/releases/0.37/docs/running-on-device-android.html @@ -0,0 +1,42 @@ +Running On Device

Running On Device #

Prerequisite: USB Debugging #

You'll need this in order to install your app on your device. First, make sure you have USB debugging enabled on your device.

Check that your device has been successfully connected by running adb devices:

$ adb devices +List of devices attached +emulator-5554 offline # Google emulator +14ed2fcc device # Physical device

Seeing device in the right column means the device is connected. Android - go figure :) You must have only one device connected.

Now you can use react-native run-android to install and launch your app on the device. If you get a "bridge configuration isn't available" error, see the Using adb reverse section below.

Setting up an Android Device #

Let's now set up an Android device to run our React Native projects.

First thing is to plug in your device and check the manufacturer code by using lsusb (on mac, you must first install lsusb). lsusb should output something like this:

$ lsusb +Bus 002 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub +Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub +Bus 001 Device 003: ID 22b8:2e76 Motorola PCS +Bus 001 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub +Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub +Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub +Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

These lines represent the USB devices currently connected to your machine.

You want the line that represents your phone. If you're in doubt, try unplugging your phone and running the command again:

$ lsusb +Bus 002 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub +Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub +Bus 001 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub +Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub +Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub +Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

You'll see that after removing the phone, the line which has the phone model ("Motorola PCS" in this case) disappeared from the list. This is the line that we care about.

Bus 001 Device 003: ID 22b8:2e76 Motorola PCS

From the above line, you want to grab the first four digits from the device ID:

22b8:2e76

In this case, it's 22b8. That's the identifier for Motorola.

You'll need to input this into your udev rules in order to get up and running:

echo SUBSYSTEM=="usb", ATTR{idVendor}=="22b8", MODE="0666", GROUP="plugdev" | sudo tee /etc/udev/rules.d/51-android-usb.rules

Make sure that you replace 22b8 with the identifier you get in the above command.

Now check that your device is properly connecting to ADB, the Android Debug Bridge, by using adb devices.

List of devices attached +TA9300GLMK device

Accessing development server from device #

You can also iterate quickly on device using the development server. Follow one of the steps described below to make your development server running on your laptop accessible for your device.

Hint

Most modern android devices don't have a hardware menu button, which we use to trigger the developer menu. In that case you can shake the device to open the dev menu (to reload, debug, etc.). Alternatively, you can run the command adb shell input keyevent 82 to open the dev menu (82 being the Menu key code).

Using adb reverse #

Note that this option is available on devices running android 5.0+ (API 21).

Have your device connected via USB with debugging enabled (see paragraph above on how to enable USB debugging on your device).

  1. Run adb reverse tcp:8081 tcp:8081
  2. You can use Reload JS and other development options with no extra configuration

Running packager on a non-standard port #

Launch the packager manually with react-native start --port [PORT]

For Android: Use the developer menu by clicking the menu button or shake. Select 'Debug server host & port for device' to set a different port.

+ + + +
+ +

For IOS: Edit the AppDelegate.m and change the line below to match the port number you're running:

jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle?platform=ios&dev=true"];

Configure your app to connect to the local dev server via Wi-Fi #

  1. Make sure your laptop and your phone are on the same Wi-Fi network.
  2. Open your React Native app on your device. You can do this the same way you'd open any other app.
  3. You'll see a red screen with an error. This is OK. The following steps will fix that.
  4. Open the Developer menu by shaking the device or running adb shell input keyevent 82 from the command line.
  5. Go to Dev Settings.
  6. Go to Debug server host for device.
  7. Type in your machine's IP address and the port of the local dev server (e.g. 10.0.1.1:8081). On Mac, you can find the IP address in System Preferences / Network. On Windows, open the command prompt and type ipconfig to find your machine's IP address (more info).
  8. Go back to the Developer menu and select Reload JS.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/running-on-device-ios.html b/releases/0.37/docs/running-on-device-ios.html new file mode 100644 index 00000000000..5c29ada9c9e --- /dev/null +++ b/releases/0.37/docs/running-on-device-ios.html @@ -0,0 +1,19 @@ +Running On Device

Running On Device #

Running an iOS app on a device requires only an Apple ID and a Mac. This guide covers only React Native specific topics.

Accessing the development server from device #

You can iterate quickly on device using the development server. First, ensure that you are on the same Wi-Fi network as your computer.

In Xcode, select your phone as build target and press "Build and run"

Hint

Shake the device to open the developer menu.

Building your app for production #

You have built a great app using React Native, and you are now itching to release it in the App Store. The process is the same as any other native iOS app, with some additional considerations to take into account.

Building an app for distribution in the App Store requires using the Release scheme in Xcode. To do this, go to Product -> Scheme -> Edit Scheme (cmd + <), make sure you're in the Run tab from the side, and set the Build Configuration dropdown to release.

Apps built for Release will automatically disable the in-app developer menu, which will prevent your users from inadvertently accessing the menu in production. It will also load the JavaScript locally, so you can put the app on a device and test whilst not connected to the computer.

Once built for release, you'll be able to distribute the app to beta testers and submit the app to the App Store.

App Transport Security #

App Transport Security is a security feature, added in iOS 9, that rejects all HTTP requests that are not sent over HTTPS. This can result in HTTP traffic being blocked, including the developer React Native server.

ATS is disabled by default in projects generated using the React Native CLI in order to make development easier. You should re-enable ATS prior to building your app for production by removing the NSAllowsArbitraryLoads entry from your Info.plist file in the ios/ folder.

To learn more about how to configure ATS on your own Xcode projects, see this post on ATS.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/running-on-simulator-ios.html b/releases/0.37/docs/running-on-simulator-ios.html new file mode 100644 index 00000000000..b4371164fc5 --- /dev/null +++ b/releases/0.37/docs/running-on-simulator-ios.html @@ -0,0 +1,19 @@ +Running On Simulator

Running On Simulator #

Starting the simulator #

Once you have your React Native project initialized, you can run react-native run-ios inside the newly created project directory. If everything is set up correctly, you should see your new app running in the iOS Simulator shortly.

Specifying a device #

You can specify the device the simulator should run with the --simulator flag, followed by the device name as a string. The default is "iPhone 6". If you wish to run your app on an iPhone 4s, just run react-native run-ios --simulator "iPhone 4s".

The device names correspond to the list of devices available in Xcode. You can check your available devices by running xcrun simctl list devices from the console.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/scrollview.html b/releases/0.37/docs/scrollview.html new file mode 100644 index 00000000000..b8bf61a235c --- /dev/null +++ b/releases/0.37/docs/scrollview.html @@ -0,0 +1,236 @@ +ScrollView

ScrollView #

Component that wraps platform ScrollView while providing +integration with touch locking "responder" system.

Keep in mind that ScrollViews must have a bounded height in order to work, +since they contain unbounded-height children into a bounded container (via +a scroll interaction). In order to bound the height of a ScrollView, either +set the height of the view directly (discouraged) or make sure all parent +views have bounded height. Forgetting to transfer {flex: 1} down the +view stack can lead to errors here, which the element inspector makes +easy to debug.

Doesn't yet support other contained responders from blocking this scroll +view from becoming the responder.

Props #

contentContainerStyle StyleSheetPropType(ViewStylePropTypes) #

These styles will be applied to the scroll view content container which +wraps all of the child views. Example:

return ( + <ScrollView contentContainerStyle={styles.contentContainer}> + </ScrollView> + ); + ... + const styles = StyleSheet.create({ + contentContainer: { + paddingVertical: 20 + } + });

horizontal bool #

When true, the scroll view's children are arranged horizontally in a row +instead of vertically in a column. The default value is false.

keyboardDismissMode enum('none', 'interactive', 'on-drag') #

Determines whether the keyboard gets dismissed in response to a drag. + - 'none' (the default), drags do not dismiss the keyboard. + - 'on-drag', the keyboard is dismissed when a drag begins. + - 'interactive', the keyboard is dismissed interactively with the drag and moves in + synchrony with the touch; dragging upwards cancels the dismissal. + On android this is not supported and it will have the same behavior as 'none'.

keyboardShouldPersistTaps bool #

When false, tapping outside of the focused text input when the keyboard +is up dismisses the keyboard. When true, the keyboard will not dismiss +automatically, and the scroll view will not catch taps, but children of +the scroll view can catch taps. The default value is false.

onContentSizeChange function #

Called when scrollable content view of the ScrollView changes.

Handler function is passed the content width and content height as parameters: (contentWidth, contentHeight)

It's implemented using onLayout handler attached to the content container +which this ScrollView renders.

onScroll function #

Fires at most once per frame during scrolling. The frequency of the +events can be controlled using the scrollEventThrottle prop.

pagingEnabled bool #

When true, the scroll view stops on multiples of the scroll view's size +when scrolling. This can be used for horizontal pagination. The default +value is false.

refreshControl element #

A RefreshControl component, used to provide pull-to-refresh +functionality for the ScrollView.

See RefreshControl.

removeClippedSubviews bool #

Experimental: When true, offscreen child views (whose overflow value is +hidden) are removed from their native backing superview when offscreen. +This can improve scrolling performance on long lists. The default value is +true.

scrollEnabled bool #

When false, the content does not scroll. +The default value is true.

showsHorizontalScrollIndicator bool #

When true, shows a horizontal scroll indicator. +The default value is true.

showsVerticalScrollIndicator bool #

When true, shows a vertical scroll indicator. +The default value is true.

style style #

backfaceVisibility enum('visible', 'hidden')
backgroundColor color
borderBottomColor color
borderBottomLeftRadius number
borderBottomRightRadius number
borderBottomWidth number
borderColor color
borderLeftColor color
borderLeftWidth number
borderRadius number
borderRightColor color
borderRightWidth number
borderStyle enum('solid', 'dotted', 'dashed')
borderTopColor color
borderTopLeftRadius number
borderTopRightRadius number
borderTopWidth number
borderWidth number
opacity number
androidelevation number

(Android-only) Sets the elevation of a view, using Android's underlying +elevation API. +This adds a drop shadow to the item and affects z-order for overlapping views. +Only supported on Android 5.0+, has no effect on earlier versions.

androidendFillColor color #

Sometimes a scrollview takes up more space than its content fills. When this is +the case, this prop will fill the rest of the scrollview with a color to avoid setting +a background and creating unnecessary overdraw. This is an advanced optimization +that is not needed in the general case.

androidscrollPerfTag string #

Tag used to log scroll performance on this scroll view. Will force +momentum events to be turned on (see sendMomentumEvents). This doesn't do +anything out of the box and you need to implement a custom native +FpsListener for it to be useful.

iosalwaysBounceHorizontal bool #

When true, the scroll view bounces horizontally when it reaches the end +even if the content is smaller than the scroll view itself. The default +value is true when horizontal={true} and false otherwise.

iosalwaysBounceVertical bool #

When true, the scroll view bounces vertically when it reaches the end +even if the content is smaller than the scroll view itself. The default +value is false when horizontal={true} and true otherwise.

iosautomaticallyAdjustContentInsets bool #

Controls whether iOS should automatically adjust the content inset +for scroll views that are placed behind a navigation bar or +tab bar/ toolbar. The default value is true.

iosbounces bool #

When true, the scroll view bounces when it reaches the end of the +content if the content is larger then the scroll view along the axis of +the scroll direction. When false, it disables all bouncing even if +the alwaysBounce* props are true. The default value is true.

iosbouncesZoom bool #

When true, gestures can drive zoom past min/max and the zoom will animate +to the min/max value at gesture end, otherwise the zoom will not exceed +the limits.

ioscanCancelContentTouches bool #

When false, once tracking starts, won't try to drag if the touch moves. +The default value is true.

ioscenterContent bool #

When true, the scroll view automatically centers the content when the +content is smaller than the scroll view bounds; when the content is +larger than the scroll view, this property has no effect. The default +value is false.

ioscontentInset {top: number, left: number, bottom: number, right: number} #

The amount by which the scroll view content is inset from the edges +of the scroll view. Defaults to {top: 0, left: 0, bottom: 0, right: 0}.

ioscontentOffset PointPropType #

Used to manually set the starting scroll offset. +The default value is {x: 0, y: 0}.

iosdecelerationRate enum('fast', 'normal'), number #

A floating-point number that determines how quickly the scroll view +decelerates after the user lifts their finger. You may also use string +shortcuts "normal" and "fast" which match the underlying iOS settings +for UIScrollViewDecelerationRateNormal and +UIScrollViewDecelerationRateFast respectively. + - normal: 0.998 (the default) + - fast: 0.99

iosdirectionalLockEnabled bool #

When true, the ScrollView will try to lock to only vertical or horizontal +scrolling while dragging. The default value is false.

iosindicatorStyle enum('default', 'black', 'white') #

The style of the scroll indicators. + - default (the default), same as black. + - black, scroll indicator is black. This style is good against a white content background. + - white, scroll indicator is white. This style is good against a black content background.

iosmaximumZoomScale number #

The maximum allowed zoom scale. The default value is 1.0.

iosminimumZoomScale number #

The minimum allowed zoom scale. The default value is 1.0.

iosonScrollAnimationEnd function #

Called when a scrolling animation ends.

iosscrollEventThrottle number #

This controls how often the scroll event will be fired while scrolling +(as a time interval in ms). A lower number yields better accuracy for code +that is tracking the scroll position, but can lead to scroll performance +problems due to the volume of information being send over the bridge. +You will not notice a difference between values set between 1-16 as the +JS run loop is synced to the screen refresh rate. If you do not need precise +scroll position tracking, set this value higher to limit the information +being sent across the bridge. The default value is zero, which results in +the scroll event being sent only once each time the view is scrolled.

iosscrollIndicatorInsets {top: number, left: number, bottom: number, right: number} #

The amount by which the scroll view indicators are inset from the edges +of the scroll view. This should normally be set to the same value as +the contentInset. Defaults to {0, 0, 0, 0}.

iosscrollsToTop bool #

When true, the scroll view scrolls to top when the status bar is tapped. +The default value is true.

iossnapToAlignment enum('start', 'center', 'end') #

When snapToInterval is set, snapToAlignment will define the relationship +of the snapping to the scroll view. + - start (the default) will align the snap at the left (horizontal) or top (vertical) + - center will align the snap in the center + - end will align the snap at the right (horizontal) or bottom (vertical)

iossnapToInterval number #

When set, causes the scroll view to stop at multiples of the value of +snapToInterval. This can be used for paginating through children +that have lengths smaller than the scroll view. Used in combination +with snapToAlignment.

iosstickyHeaderIndices [number] #

An array of child indices determining which children get docked to the +top of the screen when scrolling. For example, passing +stickyHeaderIndices={[0]} will cause the first child to be fixed to the +top of the scroll view. This property is not supported in conjunction +with horizontal={true}.

ioszoomScale number #

The current scale of the scroll view content. The default value is 1.0.

Methods #

scrollTo(y?, x?, animated?) #

Scrolls to a given x, y offset, either immediately or with a smooth animation.

Syntax:

scrollTo(options: {x: number = 0; y: number = 0; animated: boolean = true})

Note: The weird argument signature is due to the fact that, for historical reasons, +the function also accepts separate arguments as as alternative to the options object. +This is deprecated due to ambiguity (y before x), and SHOULD NOT BE USED.

scrollWithoutAnimationTo(y, x) #

Deprecated, do not use.

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + ScrollView, + StyleSheet, + Text, + TouchableOpacity, + View, + Image +} = ReactNative; + +exports.displayName = (undefined: ?string); +exports.title = '<ScrollView>'; +exports.description = 'Component that enables scrolling through child components'; +exports.examples = [ +{ + title: '<ScrollView>', + description: 'To make content scrollable, wrap it within a <ScrollView> component', + render: function() { + var _scrollView: ScrollView; + return ( + <View> + <ScrollView + ref={(scrollView) => { _scrollView = scrollView; }} + automaticallyAdjustContentInsets={false} + onScroll={() => { console.log('onScroll!'); }} + scrollEventThrottle={200} + style={styles.scrollView}> + {THUMBS.map(createThumbRow)} + </ScrollView> + <TouchableOpacity + style={styles.button} + onPress={() => { _scrollView.scrollTo({y: 0}); }}> + <Text>Scroll to top</Text> + </TouchableOpacity> + </View> + ); + } +}, { + title: '<ScrollView> (horizontal = true)', + description: 'You can display <ScrollView>\'s child components horizontally rather than vertically', + render: function() { + var _scrollView: ScrollView; + return ( + <View> + <ScrollView + ref={(scrollView) => { _scrollView = scrollView; }} + automaticallyAdjustContentInsets={false} + horizontal={true} + style={[styles.scrollView, styles.horizontalScrollView]}> + {THUMBS.map(createThumbRow)} + </ScrollView> + <TouchableOpacity + style={styles.button} + onPress={() => { _scrollView.scrollTo({x: 0}); }}> + <Text>Scroll to start</Text> + </TouchableOpacity> + </View> + ); + } +}]; + +class Thumb extends React.Component { + shouldComponentUpdate(nextProps, nextState) { + return false; + } + + render() { + return ( + <View style={styles.button}> + <Image style={styles.img} source={{uri:this.props.uri}} /> + </View> + ); + } +} + +var THUMBS = ['https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-ash3/t39.1997/p128x128/851549_767334479959628_274486868_n.png', 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851561_767334496626293_1958532586_n.png', 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-ash3/t39.1997/p128x128/851579_767334503292959_179092627_n.png', 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851589_767334513292958_1747022277_n.png', 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851563_767334559959620_1193692107_n.png', 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851593_767334566626286_1953955109_n.png', 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851591_767334523292957_797560749_n.png', 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851567_767334529959623_843148472_n.png', 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851548_767334489959627_794462220_n.png', 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851575_767334539959622_441598241_n.png', 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-ash3/t39.1997/p128x128/851573_767334549959621_534583464_n.png', 'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851583_767334573292952_1519550680_n.png']; +THUMBS = THUMBS.concat(THUMBS); // double length of THUMBS +var createThumbRow = (uri, i) => <Thumb key={i} uri={uri} />; + +var styles = StyleSheet.create({ + scrollView: { + backgroundColor: '#6A85B1', + height: 300, + }, + horizontalScrollView: { + height: 120, + }, + containerPage: { + height: 50, + width: 50, + backgroundColor: '#527FE4', + padding: 5, + }, + text: { + fontSize: 20, + color: '#888888', + left: 80, + top: 20, + height: 40, + }, + button: { + margin: 7, + padding: 5, + alignItems: 'center', + backgroundColor: '#eaeaea', + borderRadius: 3, + }, + buttonContents: { + flexDirection: 'row', + width: 64, + height: 64, + }, + img: { + width: 64, + height: 64, + } +});
\ No newline at end of file diff --git a/releases/0.37/docs/segmentedcontrolios.html b/releases/0.37/docs/segmentedcontrolios.html new file mode 100644 index 00000000000..967824bb0c3 --- /dev/null +++ b/releases/0.37/docs/segmentedcontrolios.html @@ -0,0 +1,184 @@ +SegmentedControlIOS

SegmentedControlIOS #

Use SegmentedControlIOS to render a UISegmentedControl iOS.

Programmatically changing selected index #

The selected index can be changed on the fly by assigning the +selectIndex prop to a state variable, then changing that variable. +Note that the state variable would need to be updated as the user +selects a value and changes the index, as shown in the example below.

<SegmentedControlIOS + values={['One', 'Two']} + selectedIndex={this.state.selectedIndex} + onChange={(event) => { + this.setState({selectedIndex: event.nativeEvent.selectedSegmentIndex}); + }} +/>

Props #

enabled bool #

If false the user won't be able to interact with the control. +Default value is true.

momentary bool #

If true, then selecting a segment won't persist visually. +The onValueChange callback will still work as expected.

onChange function #

Callback that is called when the user taps a segment; +passes the event as an argument

onValueChange function #

Callback that is called when the user taps a segment; +passes the segment's value as an argument

selectedIndex number #

The index in props.values of the segment to be (pre)selected.

tintColor string #

Accent color of the control.

values [string] #

The labels for the control's segment buttons, in order.

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + SegmentedControlIOS, + Text, + View, + StyleSheet +} = ReactNative; + +class BasicSegmentedControlExample extends React.Component { + render() { + return ( + <View> + <View style={{marginBottom: 10}}> + <SegmentedControlIOS values={['One', 'Two']} /> + </View> + <View> + <SegmentedControlIOS values={['One', 'Two', 'Three', 'Four', 'Five']} /> + </View> + </View> + ); + } +} + +class PreSelectedSegmentedControlExample extends React.Component { + render() { + return ( + <View> + <View> + <SegmentedControlIOS values={['One', 'Two']} selectedIndex={0} /> + </View> + </View> + ); + } +} + +class MomentarySegmentedControlExample extends React.Component { + render() { + return ( + <View> + <View> + <SegmentedControlIOS values={['One', 'Two']} momentary={true} /> + </View> + </View> + ); + } +} + +class DisabledSegmentedControlExample extends React.Component { + render() { + return ( + <View> + <View> + <SegmentedControlIOS enabled={false} values={['One', 'Two']} selectedIndex={1} /> + </View> + </View> + ); + } +} + +class ColorSegmentedControlExample extends React.Component { + render() { + return ( + <View> + <View style={{marginBottom: 10}}> + <SegmentedControlIOS tintColor="#ff0000" values={['One', 'Two', 'Three', 'Four']} selectedIndex={0} /> + </View> + <View> + <SegmentedControlIOS tintColor="#00ff00" values={['One', 'Two', 'Three']} selectedIndex={1} /> + </View> + </View> + ); + } +} + +class EventSegmentedControlExample extends React.Component { + state = { + values: ['One', 'Two', 'Three'], + value: 'Not selected', + selectedIndex: undefined + }; + + render() { + return ( + <View> + <Text style={styles.text} > + Value: {this.state.value} + </Text> + <Text style={styles.text} > + Index: {this.state.selectedIndex} + </Text> + <SegmentedControlIOS + values={this.state.values} + selectedIndex={this.state.selectedIndex} + onChange={this._onChange} + onValueChange={this._onValueChange} /> + </View> + ); + } + + _onChange = (event) => { + this.setState({ + selectedIndex: event.nativeEvent.selectedSegmentIndex, + }); + }; + + _onValueChange = (value) => { + this.setState({ + value: value, + }); + }; +} + +var styles = StyleSheet.create({ + text: { + fontSize: 14, + textAlign: 'center', + fontWeight: '500', + margin: 10, + }, +}); + +exports.title = '<SegmentedControlIOS>'; +exports.displayName = 'SegmentedControlExample'; +exports.description = 'Native segmented control'; +exports.examples = [ + { + title: 'Segmented controls can have values', + render(): React.Element<any> { return <BasicSegmentedControlExample />; } + }, + { + title: 'Segmented controls can have a pre-selected value', + render(): React.Element<any> { return <PreSelectedSegmentedControlExample />; } + }, + { + title: 'Segmented controls can be momentary', + render(): React.Element<any> { return <MomentarySegmentedControlExample />; } + }, + { + title: 'Segmented controls can be disabled', + render(): React.Element<any> { return <DisabledSegmentedControlExample />; } + }, + { + title: 'Custom colors can be provided', + render(): React.Element<any> { return <ColorSegmentedControlExample />; } + }, + { + title: 'Change events can be detected', + render(): React.Element<any> { return <EventSegmentedControlExample />; } + } +];
\ No newline at end of file diff --git a/releases/0.37/docs/settings.html b/releases/0.37/docs/settings.html new file mode 100644 index 00000000000..1c2cc45311f --- /dev/null +++ b/releases/0.37/docs/settings.html @@ -0,0 +1,19 @@ +Settings

Settings #

Methods #

static get(key) #

static set(settings) #

static watchKeys(keys, callback) #

static clearWatch(watchId) #

Properties #

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/shadow-props.html b/releases/0.37/docs/shadow-props.html new file mode 100644 index 00000000000..0e85ff8fd64 --- /dev/null +++ b/releases/0.37/docs/shadow-props.html @@ -0,0 +1,19 @@ +Shadow Props

Shadow Props #

Props #

iosshadowColor color #

Sets the drop shadow color

iosshadowOffset {width: number, height: number} #

Sets the drop shadow offset

iosshadowOpacity number #

Sets the drop shadow opacity (multiplied by the color's alpha component)

iosshadowRadius number #

Sets the drop shadow blur radius

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/signed-apk-android.html b/releases/0.37/docs/signed-apk-android.html new file mode 100644 index 00000000000..649cfbebe70 --- /dev/null +++ b/releases/0.37/docs/signed-apk-android.html @@ -0,0 +1,44 @@ +Generating Signed APK

Generating Signed APK #

Android requires that all apps be digitally signed with a certificate before they can be installed, so to distribute your Android application via Google Play store, you'll need to generate a signed release APK. The Signing Your Applications page on Android Developers documentation describes the topic in detail. This guide covers the process in brief, as well as lists the steps required to packaging the JavaScript bundle.

Generating a signing key #

You can generate a private signing key using keytool. On Windows keytool must be run from C:\Program Files\Java\jdkx.x.x_x\bin.

$ keytool -genkey -v -keystore my-release-key.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000

This command prompts you for passwords for the keystore and key, and to provide the Distinguished Name fields for your key. It then generates the keystore as a file called my-release-key.keystore.

The keystore contains a single key, valid for 10000 days. The alias is a name that you will use later when signing your app, so remember to take note of the alias.

Note: Remember to keep your keystore file private and never commit it to version control.

Setting up gradle variables #

  1. Place the my-release-key.keystore file under the android/app directory in your project folder.
  2. Edit the file ~/.gradle/gradle.properties and add the following (replace ***** with the correct keystore password, alias and key password),
MYAPP_RELEASE_STORE_FILE=my-release-key.keystore +MYAPP_RELEASE_KEY_ALIAS=my-key-alias +MYAPP_RELEASE_STORE_PASSWORD=***** +MYAPP_RELEASE_KEY_PASSWORD=*****

These are going to be global gradle variables, which we can later use in our gradle config to sign our app.

Note about saving the keystore:

Once you publish the app on the Play Store, you will need to republish your app under a different package name (losing all downloads and ratings) if you want to change the signing key at any point. So backup your keystore and don't forget the passwords.

Note about security: If you are not keen on storing your passwords in plaintext and you are running OSX, you can also store your credentials in the Keychain Access app. Then you can skip the two last rows in ~/.gradle/gradle.properties.

Adding signing config to your app's gradle config #

Edit the file android/app/build.gradle in your project folder and add the signing config,

... +android { + ... + defaultConfig { ... } + signingConfigs { + release { + storeFile file(MYAPP_RELEASE_STORE_FILE) + storePassword MYAPP_RELEASE_STORE_PASSWORD + keyAlias MYAPP_RELEASE_KEY_ALIAS + keyPassword MYAPP_RELEASE_KEY_PASSWORD + } + } + buildTypes { + release { + ... + signingConfig signingConfigs.release + } + } +} +...

Generating the release APK #

Simply run the following in a terminal:

$ cd android && ./gradlew assembleRelease

Gradle's assembleRelease will bundle all the JavaScript needed to run your app into the APK. If you need to change the way the JavaScript bundle and/or drawable resources are bundled (e.g. if you changed the default file/folder names or the general structure of the project), have a look at android/app/build.gradle to see how you can update it to reflect these changes.

The generated APK can be found under android/app/build/outputs/apk/app-release.apk, and is ready to be distributed.

Testing the release build of your app #

Before uploading the release build to the Play Store, make sure you test it thoroughly. Install it on the device using:

$ react-native run-android --variant=release

Note that --variant=release is only available if you've set up signing as described above.

You can kill any running packager instances, all your and framework JavaScript code is bundled in the APK's assets.

Enabling Proguard to reduce the size of the APK (optional) #

Proguard is a tool that can slightly reduce the size of the APK. It does this by stripping parts of the React Native Java bytecode (and its dependencies) that your app is not using.

IMPORTANT: Make sure to thoroughly test your app if you've enabled Proguard. Proguard often requires configuration specific to each native library you're using. See app/proguard-rules.pro.

To enable Proguard, edit android/app/build.gradle:

/** + * Run Proguard to shrink the Java bytecode in release builds. + */ +def enableProguardInReleaseBuilds = true

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/slider.html b/releases/0.37/docs/slider.html new file mode 100644 index 00000000000..fe06d75ac29 --- /dev/null +++ b/releases/0.37/docs/slider.html @@ -0,0 +1,180 @@ +Slider

Slider #

A component used to select a single value from a range of values.

Props #

disabled bool #

If true the user won't be able to move the slider. +Default value is false.

maximumValue number #

Initial maximum value of the slider. Default value is 1.

minimumValue number #

Initial minimum value of the slider. Default value is 0.

onSlidingComplete function #

Callback called when the user finishes changing the value (e.g. when +the slider is released).

onValueChange function #

Callback continuously called while the user is dragging the slider.

step number #

Step value of the slider. The value should be +between 0 and (maximumValue - minimumValue). +Default value is 0.

style View#style #

Used to style and layout the Slider. See StyleSheet.js and +ViewStylePropTypes.js for more info.

testID string #

Used to locate this view in UI automation tests.

value number #

Initial value of the slider. The value should be between minimumValue +and maximumValue, which default to 0 and 1 respectively. +Default value is 0.

This is not a controlled component, you don't need to update the +value during dragging.

iosmaximumTrackImage Image.propTypes.source #

Assigns a maximum track image. Only static images are supported. The +leftmost pixel of the image will be stretched to fill the track.

iosmaximumTrackTintColor string #

The color used for the track to the right of the button. Overrides the +default blue gradient image.

iosminimumTrackImage Image.propTypes.source #

Assigns a minimum track image. Only static images are supported. The +rightmost pixel of the image will be stretched to fill the track.

iosminimumTrackTintColor string #

The color used for the track to the left of the button. Overrides the +default blue gradient image.

iosthumbImage Image.propTypes.source #

Sets an image for the thumb. Only static images are supported.

iostrackImage Image.propTypes.source #

Assigns a single image for the track. Only static images are supported. +The center pixel of the image will be stretched to fill the track.

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + Slider, + Text, + StyleSheet, + View, +} = ReactNative; + +class SliderExample extends React.Component { + static defaultProps = { + value: 0, + }; + + state = { + value: this.props.value, + }; + + render() { + return ( + <View> + <Text style={styles.text} > + {this.state.value && +this.state.value.toFixed(3)} + </Text> + <Slider + {...this.props} + onValueChange={(value) => this.setState({value: value})} /> + </View> + ); + } +} + +class SlidingCompleteExample extends React.Component { + state = { + slideCompletionValue: 0, + slideCompletionCount: 0, + }; + + render() { + return ( + <View> + <SliderExample + {...this.props} + onSlidingComplete={(value) => this.setState({ + slideCompletionValue: value, + slideCompletionCount: this.state.slideCompletionCount + 1})} /> + <Text> + Completions: {this.state.slideCompletionCount} Value: {this.state.slideCompletionValue} + </Text> + </View> + ); + } +} + +var styles = StyleSheet.create({ + slider: { + height: 10, + margin: 10, + }, + text: { + fontSize: 14, + textAlign: 'center', + fontWeight: '500', + margin: 10, + }, +}); + +exports.title = '<Slider>'; +exports.displayName = 'SliderExample'; +exports.description = 'Slider input for numeric values'; +exports.examples = [ + { + title: 'Default settings', + render(): React.Element<any> { + return <SliderExample />; + } + }, + { + title: 'Initial value: 0.5', + render(): React.Element<any> { + return <SliderExample value={0.5} />; + } + }, + { + title: 'minimumValue: -1, maximumValue: 2', + render(): React.Element<any> { + return ( + <SliderExample + minimumValue={-1} + maximumValue={2} + /> + ); + } + }, + { + title: 'step: 0.25', + render(): React.Element<any> { + return <SliderExample step={0.25} />; + } + }, + { + title: 'onSlidingComplete', + render(): React.Element<any> { + return ( + <SlidingCompleteExample /> + ); + } + }, + { + title: 'Custom min/max track tint color', + platform: 'ios', + render(): React.Element<any> { + return ( + <SliderExample + minimumTrackTintColor={'red'} + maximumTrackTintColor={'green'} + /> + ); + } + }, + { + title: 'Custom thumb image', + platform: 'ios', + render(): React.Element<any> { + return <SliderExample thumbImage={require('./uie_thumb_big.png')} />; + } + }, + { + title: 'Custom track image', + platform: 'ios', + render(): React.Element<any> { + return <SliderExample trackImage={require('./slider.png')} />; + } + }, + { + title: 'Custom min/max track image', + platform: 'ios', + render(): React.Element<any> { + return ( + <SliderExample + minimumTrackImage={require('./slider-left.png')} + maximumTrackImage={require('./slider-right.png')} + /> + ); + } + }, +];
\ No newline at end of file diff --git a/releases/0.37/docs/snapshotviewios.html b/releases/0.37/docs/snapshotviewios.html new file mode 100644 index 00000000000..2e49ab21e03 --- /dev/null +++ b/releases/0.37/docs/snapshotviewios.html @@ -0,0 +1,19 @@ +SnapshotViewIOS

SnapshotViewIOS #

Props #

onSnapshotReady function #

testIdentifier string #

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/state.html b/releases/0.37/docs/state.html new file mode 100644 index 00000000000..b745b71d7de --- /dev/null +++ b/releases/0.37/docs/state.html @@ -0,0 +1,54 @@ +State

State #

There are two types of data that control a component: props and state. props are set by the parent and they are fixed throughout the lifetime of a component. For data that is going to change, we have to use state.

In general, you should initialize state in the constructor, and then call setState when you want to change it.

For example, let's say we want to make text that blinks all the time. The text itself gets set once when the blinking component gets created, so the text itself is a prop. The "whether the text is currently on or off" changes over time, so that should be kept in state.

import React, { Component } from 'react'; +import { AppRegistry, Text, View } from 'react-native'; + +class Blink extends Component { + constructor(props) { + super(props); + this.state = {showText: true}; + + // Toggle the state every second + setInterval(() => { + this.setState({ showText: !this.state.showText }); + }, 1000); + } + + render() { + let display = this.state.showText ? this.props.text : ' '; + return ( + <Text>{display}</Text> + ); + } +} + +class BlinkApp extends Component { + render() { + return ( + <View> + <Blink text='I love to blink' /> + <Blink text='Yes blinking is so great' /> + <Blink text='Why did they ever take this out of HTML' /> + <Blink text='Look at me look at me look at me' /> + </View> + ); + } +} + +AppRegistry.registerComponent('BlinkApp', () => BlinkApp);

In a real application, you probably won't be setting state with a timer. You might set state when you have new data arrive from the server, or from user input. You can also use a state container like Redux to control your data flow. In that case you would use Redux to modify your state rather than calling setState directly.

State works the same way as it does in React, so for more details on handling state, you can look at the React.Component API.

At this point, you might be annoyed that most of our examples so far use boring default black text. To make things more beautiful, you will have to learn about Style.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/statusbar.html b/releases/0.37/docs/statusbar.html new file mode 100644 index 00000000000..efd812a6590 --- /dev/null +++ b/releases/0.37/docs/statusbar.html @@ -0,0 +1,486 @@ +StatusBar

StatusBar #

Component to control the app status bar.

Usage with Navigator #

It is possible to have multiple StatusBar components mounted at the same +time. The props will be merged in the order the StatusBar components were +mounted. One use case is to specify status bar styles per route using Navigator.

<View> + <StatusBar + backgroundColor="blue" + barStyle="light-content" + /> + <Navigator + initialRoute={{statusBarHidden: true}} + renderScene={(route, navigator) => + <View> + <StatusBar hidden={route.statusBarHidden} /> + ... + </View> + } + /> + </View>

Imperative API #

For cases where using a component is not ideal, there is also an imperative +API exposed as static functions on the component. It is however not recommended +to use the static API and the component for the same prop because any value +set by the static API will get overriden by the one set by the component in +the next render.

Props #

animated bool #

If the transition between status bar property changes should be animated. +Supported for backgroundColor, barStyle and hidden.

hidden bool #

If the status bar is hidden.

androidbackgroundColor color #

The background color of the status bar.

androidtranslucent bool #

If the status bar is translucent. +When translucent is set to true, the app will draw under the status bar. +This is useful when using a semi transparent status bar color.

iosbarStyle enum('default', 'light-content', 'dark-content') #

Sets the color of the status bar text.

iosnetworkActivityIndicatorVisible bool #

If the network activity indicator should be visible.

iosshowHideTransition enum('fade', 'slide') #

The transition effect when showing and hiding the status bar using the hidden +prop. Defaults to 'fade'.

Methods #

static setHidden(hidden, animation?) #

Show or hide the status bar

Parameters:
Name and TypeDescription
hidden

boolean

The dialog's title.

[animation]

Optional animation when + changing the status bar hidden property.

static setBarStyle(style, animated?) #

Set the status bar style

Parameters:
Name and TypeDescription
style

Status bar style to set

[animated]

boolean

Animate the style change.

static setNetworkActivityIndicatorVisible(visible) #

Control the visibility of the network activity indicator

Parameters:
Name and TypeDescription
visible

boolean

Show the indicator.

static setBackgroundColor(color, animated?) #

Set the background color for the status bar

Parameters:
Name and TypeDescription
color

string

Background color.

[animated]

boolean

Animate the style change.

static setTranslucent(translucent) #

Control the translucency of the status bar

Parameters:
Name and TypeDescription
translucent

boolean

Set as translucent.

Type Definitions #

StatusBarStyle #

Status bar style

Type:
$Enum

Constants:
ValueDescription
default

Default status bar style (dark for iOS, light for Android)

light-content

Dark background, white texts and icons

dark-content

Light background, dark texts and icons

StatusBarAnimation #

Status bar animation

Type:
$Enum

Constants:
ValueDescription
none

No animation

fade

Fade animation

slide

Slide animation

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +const React = require('react'); +const ReactNative = require('react-native'); +const { + StatusBar, + StyleSheet, + Text, + TouchableHighlight, + View, +} = ReactNative; + +exports.framework = 'React'; +exports.title = '<StatusBar>'; +exports.description = 'Component for controlling the status bar'; + +const colors = [ + '#ff0000', + '#00ff00', + '#0000ff', +]; + +const barStyles = [ + 'default', + 'light-content', +]; + +const showHideTransitions = [ + 'fade', + 'slide', +]; + +function getValue<T>(values: Array<T>, index: number): T { + return values[index % values.length]; +} + +class StatusBarHiddenExample extends React.Component { + state = { + animated: true, + hidden: false, + showHideTransition: getValue(showHideTransitions, 0), + }; + + _showHideTransitionIndex = 0; + + _onChangeAnimated = () => { + this.setState({animated: !this.state.animated}); + }; + + _onChangeHidden = () => { + this.setState({hidden: !this.state.hidden}); + }; + + _onChangeTransition = () => { + this._showHideTransitionIndex++; + this.setState({ + showHideTransition: getValue(showHideTransitions, this._showHideTransitionIndex), + }); + }; + + render() { + return ( + <View> + <StatusBar + hidden={this.state.hidden} + showHideTransition={this.state.showHideTransition} + animated={this.state.animated} + /> + <TouchableHighlight + style={styles.wrapper} + onPress={this._onChangeHidden}> + <View style={styles.button}> + <Text>hidden: {this.state.hidden ? 'true' : 'false'}</Text> + </View> + </TouchableHighlight> + <TouchableHighlight + style={styles.wrapper} + onPress={this._onChangeAnimated}> + <View style={styles.button}> + <Text>animated (ios only): {this.state.animated ? 'true' : 'false'}</Text> + </View> + </TouchableHighlight> + <TouchableHighlight + style={styles.wrapper} + onPress={this._onChangeTransition}> + <View style={styles.button}> + <Text> + showHideTransition (ios only): + '{getValue(showHideTransitions, this._showHideTransitionIndex)}' + </Text> + </View> + </TouchableHighlight> + </View> + ); + } +} + +class StatusBarStyleExample extends React.Component { + _barStyleIndex = 0; + + _onChangeBarStyle = () => { + this._barStyleIndex++; + this.setState({barStyle: getValue(barStyles, this._barStyleIndex)}); + }; + + _onChangeAnimated = () => { + this.setState({animated: !this.state.animated}); + }; + + state = { + animated: true, + barStyle: getValue(barStyles, this._barStyleIndex), + }; + + render() { + return ( + <View> + <StatusBar + animated={this.state.animated} + barStyle={this.state.barStyle} + /> + <TouchableHighlight + style={styles.wrapper} + onPress={this._onChangeBarStyle}> + <View style={styles.button}> + <Text>style: '{getValue(barStyles, this._barStyleIndex)}'</Text> + </View> + </TouchableHighlight> + <TouchableHighlight + style={styles.wrapper} + onPress={this._onChangeAnimated}> + <View style={styles.button}> + <Text>animated: {this.state.animated ? 'true' : 'false'}</Text> + </View> + </TouchableHighlight> + </View> + ); + } +} + +class StatusBarNetworkActivityExample extends React.Component { + state = { + networkActivityIndicatorVisible: false, + }; + + _onChangeNetworkIndicatorVisible = () => { + this.setState({ + networkActivityIndicatorVisible: !this.state.networkActivityIndicatorVisible, + }); + }; + + render() { + return ( + <View> + <StatusBar + networkActivityIndicatorVisible={this.state.networkActivityIndicatorVisible} + /> + <TouchableHighlight + style={styles.wrapper} + onPress={this._onChangeNetworkIndicatorVisible}> + <View style={styles.button}> + <Text> + networkActivityIndicatorVisible: + {this.state.networkActivityIndicatorVisible ? 'true' : 'false'} + </Text> + </View> + </TouchableHighlight> + </View> + ); + } +} + +class StatusBarBackgroundColorExample extends React.Component { + state = { + animated: true, + backgroundColor: getValue(colors, 0), + }; + + _colorIndex = 0; + + _onChangeBackgroundColor = () => { + this._colorIndex++; + this.setState({backgroundColor: getValue(colors, this._colorIndex)}); + }; + + _onChangeAnimated = () => { + this.setState({animated: !this.state.animated}); + }; + + render() { + return ( + <View> + <StatusBar + backgroundColor={this.state.backgroundColor} + animated={this.state.animated} + /> + <TouchableHighlight + style={styles.wrapper} + onPress={this._onChangeBackgroundColor}> + <View style={styles.button}> + <Text>backgroundColor: '{getValue(colors, this._colorIndex)}'</Text> + </View> + </TouchableHighlight> + <TouchableHighlight + style={styles.wrapper} + onPress={this._onChangeAnimated}> + <View style={styles.button}> + <Text>animated: {this.state.animated ? 'true' : 'false'}</Text> + </View> + </TouchableHighlight> + </View> + ); + } +} + +class StatusBarTranslucentExample extends React.Component { + state = { + translucent: false, + }; + + _onChangeTranslucent = () => { + this.setState({ + translucent: !this.state.translucent, + }); + }; + + render() { + return ( + <View> + <StatusBar + translucent={this.state.translucent} + /> + <TouchableHighlight + style={styles.wrapper} + onPress={this._onChangeTranslucent}> + <View style={styles.button}> + <Text>translucent: {this.state.translucent ? 'true' : 'false'}</Text> + </View> + </TouchableHighlight> + </View> + ); + } +} + +class StatusBarStaticIOSExample extends React.Component { + render() { + return ( + <View> + <TouchableHighlight + style={styles.wrapper} + onPress={() => { + StatusBar.setHidden(true, 'slide'); + }}> + <View style={styles.button}> + <Text>setHidden(true, 'slide')</Text> + </View> + </TouchableHighlight> + <TouchableHighlight + style={styles.wrapper} + onPress={() => { + StatusBar.setHidden(false, 'fade'); + }}> + <View style={styles.button}> + <Text>setHidden(false, 'fade')</Text> + </View> + </TouchableHighlight> + <TouchableHighlight + style={styles.wrapper} + onPress={() => { + StatusBar.setBarStyle('default', true); + }}> + <View style={styles.button}> + <Text>setBarStyle('default', true)</Text> + </View> + </TouchableHighlight> + <TouchableHighlight + style={styles.wrapper} + onPress={() => { + StatusBar.setBarStyle('light-content', true); + }}> + <View style={styles.button}> + <Text>setBarStyle('light-content', true)</Text> + </View> + </TouchableHighlight> + <TouchableHighlight + style={styles.wrapper} + onPress={() => { + StatusBar.setNetworkActivityIndicatorVisible(true); + }}> + <View style={styles.button}> + <Text>setNetworkActivityIndicatorVisible(true)</Text> + </View> + </TouchableHighlight> + <TouchableHighlight + style={styles.wrapper} + onPress={() => { + StatusBar.setNetworkActivityIndicatorVisible(false); + }}> + <View style={styles.button}> + <Text>setNetworkActivityIndicatorVisible(false)</Text> + </View> + </TouchableHighlight> + </View> + ); + } +} + +class StatusBarStaticAndroidExample extends React.Component { + render() { + return ( + <View> + <TouchableHighlight + style={styles.wrapper} + onPress={() => { + StatusBar.setHidden(true); + }}> + <View style={styles.button}> + <Text>setHidden(true)</Text> + </View> + </TouchableHighlight> + <TouchableHighlight + style={styles.wrapper} + onPress={() => { + StatusBar.setHidden(false); + }}> + <View style={styles.button}> + <Text>setHidden(false)</Text> + </View> + </TouchableHighlight> + <TouchableHighlight + style={styles.wrapper} + onPress={() => { + StatusBar.setBackgroundColor('#ff00ff', true); + }}> + <View style={styles.button}> + <Text>setBackgroundColor('#ff00ff', true)</Text> + </View> + </TouchableHighlight> + <TouchableHighlight + style={styles.wrapper} + onPress={() => { + StatusBar.setBackgroundColor('#00ff00', true); + }}> + <View style={styles.button}> + <Text>setBackgroundColor('#00ff00', true)</Text> + </View> + </TouchableHighlight> + <TouchableHighlight + style={styles.wrapper} + onPress={() => { + StatusBar.setTranslucent(true); + StatusBar.setBackgroundColor('rgba(0, 0, 0, 0.4)', true); + }}> + <View style={styles.button}> + <Text>setTranslucent(true) and setBackgroundColor('rgba(0, 0, 0, 0.4)', true)</Text> + </View> + </TouchableHighlight> + <TouchableHighlight + style={styles.wrapper} + onPress={() => { + StatusBar.setTranslucent(false); + StatusBar.setBackgroundColor('black', true); + }}> + <View style={styles.button}> + <Text>setTranslucent(false) and setBackgroundColor('black', true)</Text> + </View> + </TouchableHighlight> + </View> + ); + } +} + +const examples = [{ + title: 'StatusBar hidden', + render() { + return <StatusBarHiddenExample />; + }, +}, { + title: 'StatusBar style', + render() { + return <StatusBarStyleExample />; + }, + platform: 'ios', +}, { + title: 'StatusBar network activity indicator', + render() { + return <StatusBarNetworkActivityExample />; + }, + platform: 'ios', +}, { + title: 'StatusBar background color', + render() { + return <StatusBarBackgroundColorExample />; + }, + platform: 'android', +}, { + title: 'StatusBar background color', + render() { + return <StatusBarTranslucentExample />; + }, + platform: 'android', +}, { + title: 'StatusBar static API', + render() { + return <StatusBarStaticIOSExample />; + }, + platform: 'ios', +}, { + title: 'StatusBar static API', + render() { + return <StatusBarStaticAndroidExample />; + }, + platform: 'android', +}, { + title: 'StatusBar dimensions', + render() { + return ( + <View> + <Text>Height: {StatusBar.currentHeight} pts</Text> + </View> + ); + }, + platform: 'android', +}]; + +exports.examples = examples; + +var styles = StyleSheet.create({ + wrapper: { + borderRadius: 5, + marginBottom: 5, + }, + button: { + borderRadius: 5, + backgroundColor: '#eeeeee', + padding: 10, + }, + title: { + marginTop: 16, + marginBottom: 8, + fontWeight: 'bold', + } +});
\ No newline at end of file diff --git a/releases/0.37/docs/statusbarios.html b/releases/0.37/docs/statusbarios.html new file mode 100644 index 00000000000..819ce20cacf --- /dev/null +++ b/releases/0.37/docs/statusbarios.html @@ -0,0 +1,19 @@ +StatusBarIOS

StatusBarIOS #

Use StatusBar for mutating the status bar.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/style.html b/releases/0.37/docs/style.html new file mode 100644 index 00000000000..f3e9aceaed4 --- /dev/null +++ b/releases/0.37/docs/style.html @@ -0,0 +1,47 @@ +Style

Style #

With React Native, you don't use a special language or syntax for defining styles. You just style your application using JavaScript. All of the core components accept a prop named style. The style names and values usually match how CSS works on the web, except names are written like backgroundColor instead of like background-color.

The style prop can be a plain old JavaScript object. That's the simplest and what we usually use for example code. You can also pass an array of styles - the last style in the array has precedence, so you can use this to inherit styles.

As a component grows in complexity, it is often cleaner to use StyleSheet.create to define several styles in one place. Here's an example:

import React, { Component } from 'react'; +import { AppRegistry, StyleSheet, Text, View } from 'react-native'; + +class LotsOfStyles extends Component { + render() { + return ( + <View> + <Text style={styles.red}>just red</Text> + <Text style={styles.bigblue}>just bigblue</Text> + <Text style={[styles.bigblue, styles.red]}>bigblue, then red</Text> + <Text style={[styles.red, styles.bigblue]}>red, then bigblue</Text> + </View> + ); + } +} + +const styles = StyleSheet.create({ + bigblue: { + color: 'blue', + fontWeight: 'bold', + fontSize: 30, + }, + red: { + color: 'red', + }, +}); + +AppRegistry.registerComponent('LotsOfStyles', () => LotsOfStyles);

One common pattern is to make your component accept a style prop which in +turn is used to style subcomponents. You can use this to make styles "cascade" the way they do in CSS.

There are a lot more ways to customize text style. Check out the Text component reference for a complete list.

Now you can make your text beautiful. The next step in becoming a style master is to learn how to control component size.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/stylesheet.html b/releases/0.37/docs/stylesheet.html new file mode 100644 index 00000000000..b1c70ecdd5c --- /dev/null +++ b/releases/0.37/docs/stylesheet.html @@ -0,0 +1,78 @@ +StyleSheet

StyleSheet #

A StyleSheet is an abstraction similar to CSS StyleSheets

Create a new StyleSheet:

var styles = StyleSheet.create({ + container: { + borderRadius: 4, + borderWidth: 0.5, + borderColor: '#d6d7da', + }, + title: { + fontSize: 19, + fontWeight: 'bold', + }, + activeTitle: { + color: 'red', + }, +});

Use a StyleSheet:

<View style={styles.container}> + <Text style={[styles.title, this.props.isActive && styles.activeTitle]} /> +</View>

Code quality:

  • By moving styles away from the render function, you're making the code +easier to understand.
  • Naming the styles is a good way to add meaning to the low level components +in the render function.

Performance:

  • Making a stylesheet from a style object makes it possible to refer to it +by ID instead of creating a new style object every time.
  • It also allows to send the style only once through the bridge. All +subsequent uses are going to refer an id (not implemented yet).

Methods #

static create(obj) #

Creates a StyleSheet style reference from the given object.

Properties #

hairlineWidth: CallExpression #

This is defined as the width of a thin line on the platform. It can be +used as the thickness of a border or division between two elements. +Example:

{ + borderBottomColor: '#bbb', + borderBottomWidth: StyleSheet.hairlineWidth + }

This constant will always be a round number of pixels (so a line defined +by it look crisp) and will try to match the standard width of a thin line +on the underlying platform. However, you should not rely on it being a +constant size, because on different platforms and screen densities its +value may be calculated differently.

A line with hairline width may not be visible if your simulator is downscaled.

absoluteFill: CallExpression #

A very common pattern is to create overlays with position absolute and zero positioning, +so absoluteFill can be used for convenience and to reduce duplication of these repeated +styles.

absoluteFillObject: ObjectExpression #

Sometimes you may want absoluteFill but with a couple tweaks - absoluteFillObject can be +used to create a customized entry in a StyleSheet, e.g.:

const styles = StyleSheet.create({ + wrapper: { + ...StyleSheet.absoluteFillObject, + top: 10, + backgroundColor: 'transparent', + }, + });

flatten: CallExpression #

Flattens an array of style objects, into one aggregated style object. +Alternatively, this method can be used to lookup IDs, returned by +StyleSheet.register.

NOTE: Exercise caution as abusing this can tax you in terms of +optimizations.

IDs enable optimizations through the bridge and memory in general. Refering +to style objects directly will deprive you of these optimizations.

Example:

var styles = StyleSheet.create({ + listItem: { + flex: 1, + fontSize: 16, + color: 'white' + }, + selectedListItem: { + color: 'green' + } +}); + +StyleSheet.flatten([styles.listItem, styles.selectedListItem]) +// returns { flex: 1, fontSize: 16, color: 'green' }

Alternative use:

StyleSheet.flatten(styles.listItem); +// return { flex: 1, fontSize: 16, color: 'white' } +// Simply styles.listItem would return its ID (number)

This method internally uses StyleSheetRegistry.getStyleByID(style) +to resolve style objects represented by IDs. Thus, an array of style +objects (instances of StyleSheet.create), are individually resolved to, +their respective objects, merged as one and then returned. This also explains +the alternative use.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/switch.html b/releases/0.37/docs/switch.html new file mode 100644 index 00000000000..ff131d0cba8 --- /dev/null +++ b/releases/0.37/docs/switch.html @@ -0,0 +1,165 @@ +Switch

Switch #

Renders a boolean input.

This is a controlled component that requires an onValueChange callback that +updates the value prop in order for the component to reflect user actions. +If the value prop is not updated, the component will continue to render +the supplied value prop instead of the expected result of any user actions.

@keyword checkbox +@keyword toggle

Props #

disabled bool #

If true the user won't be able to toggle the switch. +Default value is false.

onValueChange function #

Invoked with the new value when the value changes.

testID string #

Used to locate this view in end-to-end tests.

value bool #

The value of the switch. If true the switch will be turned on. +Default value is false.

iosonTintColor color #

Background color when the switch is turned on.

iosthumbTintColor color #

Color of the foreground switch grip.

iostintColor color #

Border color when the switch is turned off.

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + Platform, + Switch, + Text, + View +} = ReactNative; + +class BasicSwitchExample extends React.Component { + state = { + trueSwitchIsOn: true, + falseSwitchIsOn: false, + }; + + render() { + return ( + <View> + <Switch + onValueChange={(value) => this.setState({falseSwitchIsOn: value})} + style={{marginBottom: 10}} + value={this.state.falseSwitchIsOn} /> + <Switch + onValueChange={(value) => this.setState({trueSwitchIsOn: value})} + value={this.state.trueSwitchIsOn} /> + </View> + ); + } +} + +class DisabledSwitchExample extends React.Component { + render() { + return ( + <View> + <Switch + disabled={true} + style={{marginBottom: 10}} + value={true} /> + <Switch + disabled={true} + value={false} /> + </View> + ); + } +} + +class ColorSwitchExample extends React.Component { + state = { + colorTrueSwitchIsOn: true, + colorFalseSwitchIsOn: false, + }; + + render() { + return ( + <View> + <Switch + onValueChange={(value) => this.setState({colorFalseSwitchIsOn: value})} + onTintColor="#00ff00" + style={{marginBottom: 10}} + thumbTintColor="#0000ff" + tintColor="#ff0000" + value={this.state.colorFalseSwitchIsOn} /> + <Switch + onValueChange={(value) => this.setState({colorTrueSwitchIsOn: value})} + onTintColor="#00ff00" + thumbTintColor="#0000ff" + tintColor="#ff0000" + value={this.state.colorTrueSwitchIsOn} /> + </View> + ); + } +} + +class EventSwitchExample extends React.Component { + state = { + eventSwitchIsOn: false, + eventSwitchRegressionIsOn: true, + }; + + render() { + return ( + <View style={{ flexDirection: 'row', justifyContent: 'space-around' }}> + <View> + <Switch + onValueChange={(value) => this.setState({eventSwitchIsOn: value})} + style={{marginBottom: 10}} + value={this.state.eventSwitchIsOn} /> + <Switch + onValueChange={(value) => this.setState({eventSwitchIsOn: value})} + style={{marginBottom: 10}} + value={this.state.eventSwitchIsOn} /> + <Text>{this.state.eventSwitchIsOn ? 'On' : 'Off'}</Text> + </View> + <View> + <Switch + onValueChange={(value) => this.setState({eventSwitchRegressionIsOn: value})} + style={{marginBottom: 10}} + value={this.state.eventSwitchRegressionIsOn} /> + <Switch + onValueChange={(value) => this.setState({eventSwitchRegressionIsOn: value})} + style={{marginBottom: 10}} + value={this.state.eventSwitchRegressionIsOn} /> + <Text>{this.state.eventSwitchRegressionIsOn ? 'On' : 'Off'}</Text> + </View> + </View> + ); + } +} + +var examples = [ + { + title: 'Switches can be set to true or false', + render(): React.Element<any> { return <BasicSwitchExample />; } + }, + { + title: 'Switches can be disabled', + render(): React.Element<any> { return <DisabledSwitchExample />; } + }, + { + title: 'Change events can be detected', + render(): React.Element<any> { return <EventSwitchExample />; } + }, + { + title: 'Switches are controlled components', + render(): React.Element<any> { return <Switch />; } + } +]; + +if (Platform.OS === 'ios') { + examples.push({ + title: 'Custom colors can be provided', + render(): React.Element<any> { return <ColorSwitchExample />; } + }); +} + +exports.title = '<Switch>'; +exports.displayName = 'SwitchExample'; +exports.description = 'Native boolean input'; +exports.examples = examples;
\ No newline at end of file diff --git a/releases/0.37/docs/systrace.html b/releases/0.37/docs/systrace.html new file mode 100644 index 00000000000..f526c1cbca0 --- /dev/null +++ b/releases/0.37/docs/systrace.html @@ -0,0 +1,30 @@ +Systrace

Systrace #

Methods #

static setEnabled(enabled) #

static beginEvent(profileName?, args?) #

beginEvent/endEvent for starting and then ending a profile within the same call stack frame

static endEvent(0) #

static beginAsyncEvent(profileName?) #

beginAsyncEvent/endAsyncEvent for starting and then ending a profile where the end can either +occur on another thread or out of the current stack frame, eg await +the returned cookie variable should be used as input into the endAsyncEvent call to end the profile

static endAsyncEvent(profileName?, cookie?) #

static counterEvent(profileName?, value?) #

counterEvent registers the value to the profileName on the systrace timeline

static attachToRelayProfiler(relayProfiler) #

Relay profiles use await calls, so likely occur out of current stack frame +therefore async variant of profiling is used

static swizzleJSON(0) #

This is not called by default due to perf overhead but it's useful +if you want to find traces which spend too much time in JSON.

static measureMethods(object, objectName, methodNames) #

Measures multiple methods of a class. For example, you can do: +Systrace.measureMethods(JSON, 'JSON', ['parse', 'stringify']);

@param object +@param objectName +@param methodNames Map from method names to method display names.

static measure(objName, fnName, func) #

Returns an profiled version of the input function. For example, you can: +JSON.parse = Systrace.measure('JSON', 'parse', JSON.parse);

@param objName +@param fnName +@param {function} func +@return {function} replacement function

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/tabbarios-item.html b/releases/0.37/docs/tabbarios-item.html new file mode 100644 index 00000000000..a0b3c2ff33f --- /dev/null +++ b/releases/0.37/docs/tabbarios-item.html @@ -0,0 +1,26 @@ +TabBarIOS.Item

TabBarIOS.Item #

Props #

badge string, number #

Little red bubble that sits at the top right of the icon.

icon Image.propTypes.source #

A custom icon for the tab. It is ignored when a system icon is defined.

onPress function #

Callback when this tab is being selected, you should change the state of your +component to set selected={true}.

renderAsOriginal bool #

If set to true it renders the image as original, +it defaults to being displayed as a template

selected bool #

It specifies whether the children are visible or not. If you see a +blank content, you probably forgot to add a selected one.

selectedIcon Image.propTypes.source #

A custom icon when the tab is selected. It is ignored when a system +icon is defined. If left empty, the icon will be tinted in blue.

style View#style #

React style object.

systemIcon enum('bookmarks', 'contacts', 'downloads', 'favorites', 'featured', 'history', 'more', 'most-recent', 'most-viewed', 'recents', 'search', 'top-rated') #

Items comes with a few predefined system icons. Note that if you are +using them, the title and selectedIcon will be overridden with the +system ones.

title string #

Text that appears under the icon. It is ignored when a system icon +is defined.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/tabbarios.html b/releases/0.37/docs/tabbarios.html new file mode 100644 index 00000000000..9efd015c7e6 --- /dev/null +++ b/releases/0.37/docs/tabbarios.html @@ -0,0 +1,117 @@ +TabBarIOS

TabBarIOS #

Props #

barTintColor color #

Background color of the tab bar

itemPositioning enum('fill', 'center', 'auto') #

Specifies tab bar item positioning. Available values are: +- fill - distributes items across the entire width of the tab bar +- center - centers item in the available tab bar space +- auto (default) - distributes items dynamically according to the +user interface idiom. In a horizontally compact environment (e.g. iPhone 5) +this value defaults to fill, in a horizontally regular one (e.g. iPad) +it defaults to center.

tintColor color #

Color of the currently selected tab icon

translucent bool #

A Boolean value that indicates whether the tab bar is translucent

unselectedTintColor color #

Color of text on unselected tabs

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + StyleSheet, + TabBarIOS, + Text, + View, +} = ReactNative; + +var base64Icon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAABLCAQAAACSR7JhAAADtUlEQVR4Ac3YA2Bj6QLH0XPT1Fzbtm29tW3btm3bfLZtv7e2ObZnms7d8Uw098tuetPzrxv8wiISrtVudrG2JXQZ4VOv+qUfmqCGGl1mqLhoA52oZlb0mrjsnhKpgeUNEs91Z0pd1kvihA3ULGVHiQO2narKSHKkEMulm9VgUyE60s1aWoMQUbpZOWE+kaqs4eLEjdIlZTcFZB0ndc1+lhB1lZrIuk5P2aib1NBpZaL+JaOGIt0ls47SKzLC7CqrlGF6RZ09HGoNy1lYl2aRSWL5GuzqWU1KafRdoRp0iOQEiDzgZPnG6DbldcomadViflnl/cL93tOoVbsOLVM2jylvdWjXolWX1hmfZbGR/wjypDjFLSZIRov09BgYmtUqPQPlQrPapecLgTIy0jMgPKtTeob2zWtrGH3xvjUkPCtNg/tm1rjwrMa+mdUkPd3hWbH0jArPGiU9ufCsNNWFZ40wpwn+62/66R2RUtoso1OB34tnLOcy7YB1fUdc9e0q3yru8PGM773vXsuZ5YIZX+5xmHwHGVvlrGPN6ZSiP1smOsMMde40wKv2VmwPPVXNut4sVpUreZiLBHi0qln/VQeI/LTMYXpsJtFiclUN+5HVZazim+Ky+7sAvxWnvjXrJFneVtLWLyPJu9K3cXLWeOlbMTlrIelbMDlrLenrjEQOtIF+fuI9xRp9ZBFp6+b6WT8RrxEpdK64BuvHgDk+vUy+b5hYk6zfyfs051gRoNO1usU12WWRWL73/MMEy9pMi9qIrR4ZpV16Rrvduxazmy1FSvuFXRkqTnE7m2kdb5U8xGjLw/spRr1uTov4uOgQE+0N/DvFrG/Jt7i/FzwxbA9kDanhf2w+t4V97G8lrT7wc08aA2QNUkuTfW/KimT01wdlfK4yEw030VfT0RtZbzjeMprNq8m8tnSTASrTLti64oBNdpmMQm0eEwvfPwRbUBywG5TzjPCsdwk3IeAXjQblLCoXnDVeoAz6SfJNk5TTzytCNZk/POtTSV40NwOFWzw86wNJRpubpXsn60NJFlHeqlYRbslqZm2jnEZ3qcSKgm0kTli3zZVS7y/iivZTweYXJ26Y+RTbV1zh3hYkgyFGSTKPfRVbRqWWVReaxYeSLarYv1Qqsmh1s95S7G+eEWK0f3jYKTbV6bOwepjfhtafsvUsqrQvrGC8YhmnO9cSCk3yuY984F1vesdHYhWJ5FvASlacshUsajFt2mUM9pqzvKGcyNJW0arTKN1GGGzQlH0tXwLDgQTurS8eIQAAAABJRU5ErkJggg=='; + +class TabBarExample extends React.Component { + static title = '<TabBarIOS>'; + static description = 'Tab-based navigation.'; + static displayName = 'TabBarExample'; + + state = { + selectedTab: 'redTab', + notifCount: 0, + presses: 0, + }; + + _renderContent = (color: string, pageText: string, num?: number) => { + return ( + <View style={[styles.tabContent, {backgroundColor: color}]}> + <Text style={styles.tabText}>{pageText}</Text> + <Text style={styles.tabText}>{num} re-renders of the {pageText}</Text> + </View> + ); + }; + + render() { + return ( + <TabBarIOS + unselectedTintColor="yellow" + tintColor="white" + barTintColor="darkslateblue"> + <TabBarIOS.Item + title="Blue Tab" + icon={{uri: base64Icon, scale: 3}} + selected={this.state.selectedTab === 'blueTab'} + onPress={() => { + this.setState({ + selectedTab: 'blueTab', + }); + }}> + {this._renderContent('#414A8C', 'Blue Tab')} + </TabBarIOS.Item> + <TabBarIOS.Item + systemIcon="history" + badge={this.state.notifCount > 0 ? this.state.notifCount : undefined} + selected={this.state.selectedTab === 'redTab'} + onPress={() => { + this.setState({ + selectedTab: 'redTab', + notifCount: this.state.notifCount + 1, + }); + }}> + {this._renderContent('#783E33', 'Red Tab', this.state.notifCount)} + </TabBarIOS.Item> + <TabBarIOS.Item + icon={require('./flux.png')} + selectedIcon={require('./relay.png')} + renderAsOriginal + title="More" + selected={this.state.selectedTab === 'greenTab'} + onPress={() => { + this.setState({ + selectedTab: 'greenTab', + presses: this.state.presses + 1 + }); + }}> + {this._renderContent('#21551C', 'Green Tab', this.state.presses)} + </TabBarIOS.Item> + </TabBarIOS> + ); + } +} + +var styles = StyleSheet.create({ + tabContent: { + flex: 1, + alignItems: 'center', + }, + tabText: { + color: 'white', + margin: 50, + }, +}); + +module.exports = TabBarExample;
\ No newline at end of file diff --git a/releases/0.37/docs/testing.html b/releases/0.37/docs/testing.html new file mode 100644 index 00000000000..6aa6ab4f23e --- /dev/null +++ b/releases/0.37/docs/testing.html @@ -0,0 +1,22 @@ +Testing

Testing #

Running Tests and Contributing #

The React Native repo has several tests you can run to verify you haven't caused a regression with your PR. These tests are run with the Travis and CircleCI continuous integration systems, and will automatically post the results to your PR.

We don't have perfect test coverage of course, especially for complex end-to-end interactions with the user, so many changes will still require significant manual verification, but we would love it if you want to help us increase our test coverage and add more tests and test cases!

Jest Tests #

Jest tests are JS-only tests run on the command line with node. The tests themselves live in the __tests__ directories of the files they test, and there is a large emphasis on aggressively mocking out functionality that is not under test for failure isolation and maximum speed. You can run the existing React Native jest tests with

npm test

from the react-native root, and we encourage you to add your own tests for any components you want to contribute to. See getImageSource-test.js for a basic example.

To use Jest for your react-native projects we recommend following the React-Native Tutorial on the Jest website.

Unit tests (Android) #

React Native uses the Buck build tool to run tests. Unit tests run locally on your machine, no emulator is needed. To run the tests:

$ cd react-native +$ ./scripts/run-android-local-unit-tests.sh

Integration tests (Android) #

React Native uses the Buck build tool to run tests. Integration tests run on an emulator / device and verify that modules and components, as well as the core parts of React Native (such as the bridge) work well end-to-end.

Make sure you have the path to the Android NDK set up, see Prerequisites.

To run the tests:

$ cd react-native +$ npm install +$ ./scripts/run-android-local-integration-tests.sh

Integration Tests (iOS) #

React Native provides facilities to make it easier to test integrated components that require both native and JS components to communicate across the bridge. The two main components are RCTTestRunner and RCTTestModule. RCTTestRunner sets up the ReactNative environment and provides facilities to run the tests as XCTestCases in Xcode (runTest:module is the simplest method). RCTTestModule is exported to JS as NativeModules.TestModule. The tests themselves are written in JS, and must call TestModule.markTestCompleted() when they are done, otherwise the test will timeout and fail. Test failures are primarily indicated by throwing a JS exception. It is also possible to test error conditions with runTest:module:initialProps:expectErrorRegex: or runTest:module:initialProps:expectErrorBlock: which will expect an error to be thrown and verify the error matches the provided criteria. See IntegrationTestHarnessTest.js, UIExplorerIntegrationTests.m, and IntegrationTestsApp.js for example usage and integration points.

You can run integration tests locally with cmd+U in the IntegrationTest and UIExplorer apps in Xcode.

Screenshot/Snapshot Tests (iOS) #

A common type of integration test is the snapshot test. These tests render a component, and verify snapshots of the screen against reference images using TestModule.verifySnapshot(), using the FBSnapshotTestCase library behind the scenes. Reference images are recorded by setting recordMode = YES on the RCTTestRunner, then running the tests. Snapshots will differ slightly between 32 and 64 bit, and various OS versions, so it's recommended that you enforce tests are run with the correct configuration. It's also highly recommended that all network data be mocked out, along with other potentially troublesome dependencies. See SimpleSnapshotTest for a basic example.

If you make a change that affects a snapshot test in a PR, such as adding a new example case to one of the examples that is snapshotted, you'll need to re-record the snapshot reference image. To do this, simply change to _runner.recordMode = YES; in UIExplorer/UIExplorerSnapshotTests.m, re-run the failing tests, then flip record back to NO and submit/update your PR and wait to see if the Travis build passes.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/text.html b/releases/0.37/docs/text.html new file mode 100644 index 00000000000..777b1dacd18 --- /dev/null +++ b/releases/0.37/docs/text.html @@ -0,0 +1,1112 @@ +Text

Text #

A React component for displaying text.

Text supports nesting, styling, and touch handling.

In the following example, the nested title and body text will inherit the fontFamily from +styles.baseText, but the title provides its own additional styles. The title and body will +stack on top of each other on account of the literal newlines:

import React, { Component } from 'react'; +import { AppRegistry, Text, StyleSheet } from 'react-native'; + +class TextInANest extends Component { + constructor(props) { + super(props); + this.state = { + titleText: "Bird's Nest", + bodyText: 'This is not really a bird nest.' + }; + } + + render() { + return ( + <Text style={styles.baseText}> + <Text style={styles.titleText} onPress={this.onPressTitle}> + {this.state.titleText}{'\n'}{'\n'} + </Text> + <Text numberOfLines={5}> + {this.state.bodyText} + </Text> + </Text> + ); + } +} + +const styles = StyleSheet.create({ + baseText: { + fontFamily: 'Cochin', + }, + titleText: { + fontSize: 20, + fontWeight: 'bold', + }, +}); + +// App registration and rendering +AppRegistry.registerComponent('TextInANest', () => TextInANest);

Props #

accessible bool #

When set to true, indicates that the view is an accessibility element. The default value +for a Text element is true.

See the +Accessibility guide +for more information.

ellipsizeMode enum('head', 'middle', 'tail', 'clip') #

This can be one of the following values:

  • head - The line is displayed so that the end fits in the container and the missing text +at the beginning of the line is indicated by an ellipsis glyph. e.g., "...wxyz"
  • middle - The line is displayed so that the beginning and end fit in the container and the +missing text in the middle is indicated by an ellipsis glyph. "ab...yz"
  • tail - The line is displayed so that the beginning fits in the container and the +missing text at the end of the line is indicated by an ellipsis glyph. e.g., "abcd..."
  • clip - Lines are not drawn past the edge of the text container.

The default is tail.

numberOfLines must be set in conjunction with this prop.

clip is working only for iOS

numberOfLines number #

Used to truncate the text with an ellipsis after computing the text +layout, including line wrapping, such that the total number of lines +does not exceed this number.

This prop is commonly used with ellipsizeMode.

onLayout function #

Invoked on mount and layout changes with

{nativeEvent: {layout: {x, y, width, height}}}

onLongPress function #

This function is called on long press.

e.g., `onLongPress={this.increaseSize}>``

onPress function #

This function is called on press.

e.g., `onPress={() => console.log('1st')}``

style style #

color color
fontFamily string
fontSize number
fontStyle enum('normal', 'italic')
fontWeight enum('normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900')

Specifies font weight. The values 'normal' and 'bold' are supported for +most fonts. Not all fonts have a variant for each of the numeric values, +in that case the closest one is chosen.

lineHeight number
textAlign enum('auto', 'left', 'right', 'center', 'justify')

Specifies text alignment. The value 'justify' is only supported on iOS and +fallbacks to left on Android.

textDecorationLine enum('none', 'underline', 'line-through', 'underline line-through')
textShadowColor color
textShadowOffset {width: number, height: number}
textShadowRadius number
androidtextAlignVertical enum('auto', 'top', 'bottom', 'center')
iosfontVariant [enum('small-caps', 'oldstyle-nums', 'lining-nums', 'tabular-nums', 'proportional-nums')]
iosletterSpacing number
iostextDecorationColor color
iostextDecorationStyle enum('solid', 'double', 'dotted', 'dashed')
ioswritingDirection enum('auto', 'ltr', 'rtl')

testID string #

Used to locate this view in end-to-end tests.

androidselectable bool #

Lets the user select text, to use the native copy and paste functionality.

iosadjustsFontSizeToFit bool #

Specifies whether font should be scaled down automatically to fit given style constraints.

iosallowFontScaling bool #

Specifies whether fonts should scale to respect Text Size accessibility setting on iOS. The +default is true.

iosminimumFontScale number #

Specifies smallest possible scale a font can reach when adjustsFontSizeToFit is enabled. (values 0.01-1.0).

iossuppressHighlighting bool #

When true, no visual change is made when text is pressed down. By +default, a gray oval highlights the text on press down.

You can edit the content above on GitHub and send us a pull request!

Description #

Nested Text #

Both iOS and Android allow you to display formatted text by annotating ranges of a string with specific formatting like bold or colored text (NSAttributedString on iOS, SpannableString on Android). In practice, this is very tedious. For React Native, we decided to use web paradigm for this where you can nest text to achieve the same effect.

import React, { Component } from 'react'; +import { AppRegistry, Text } from 'react-native'; + +class BoldAndBeautiful extends Component { + render() { + return ( + <Text style={{fontWeight: 'bold'}}> + I am bold + <Text style={{color: 'red'}}> + and red + </Text> + </Text> + ); + } +} + +AppRegistry.registerComponent('BoldAndBeautiful', () => BoldAndBeautiful);

Behind the scenes, React Native converts this to a flat NSAttributedString or SpannableString that contains the following information:

"I am bold and red" +0-9: bold +9-17: bold, red

Nested Views (iOS Only) #

On iOS, you can nest views within your Text component. Here's an example:

import React, { Component } from 'react'; +import { AppRegistry, Text, View } from 'react-native'; + +class BlueIsCool extends Component { + render() { + return ( + <Text> + There is a blue square + <View style={{width: 50, height: 50, backgroundColor: 'steelblue'}} /> + in between my text. + </Text> + ); + } +} + +AppRegistry.registerComponent('BlueIsCool', () => BlueIsCool);

In order to use this feature, you must give the view a width and a height.

Containers #

The <Text> element is special relative to layout: everything inside is no longer using the flexbox layout but using text layout. This means that elements inside of a <Text> are no longer rectangles, but wrap when they see the end of the line.

<Text> + <Text>First part and </Text> + <Text>second part</Text> +</Text> +// Text container: all the text flows as if it was one +// |First part | +// |and second | +// |part | + +<View> + <Text>First part and </Text> + <Text>second part</Text> +</View> +// View container: each text is its own block +// |First part | +// |and | +// |second part|

Limited Style Inheritance #

On the web, the usual way to set a font family and size for the entire document is to write:

/* CSS, *not* React Native */ +html { + font-family: 'lucida grande', tahoma, verdana, arial, sans-serif; + font-size: 11px; + color: #141823; +}

When the browser is trying to render a text node, it's going to go all the way up to the root element of the tree and find an element with a font-size attribute. An unexpected property of this system is that any node can have font-size attribute, including a <div>. This was designed for convenience, even though not really semantically correct.

In React Native, we are more strict about it: you must wrap all the text nodes inside of a <Text> component; you cannot have a text node directly under a <View>.

// BAD: will raise exception, can't have a text node as child of a <View> +<View> + Some text +</View> + +// GOOD +<View> + <Text> + Some text + </Text> +</View>

You also lose the ability to set up a default font for an entire subtree. The recommended way to use consistent fonts and sizes across your application is to create a component MyAppText that includes them and use this component across your app. You can also use this component to make more specific components like MyAppHeaderText for other kinds of text.

<View> + <MyAppText>Text styled with the default font for the entire application</MyAppText> + <MyAppHeaderText>Text styled as a header</MyAppHeaderText> +</View>

Assuming that MyAppText is a component that simply renders out its children into a Text component with styling, then MyAppHeaderText can be defined as follows:

class MyAppHeaderText extends Component { + render() { + <MyAppText> + <Text style={{fontSize: 20}}> + {this.props.children} + </Text> + </MyAppText> + } +}

Composing MyAppText in this way ensures that we get the styles from a top-level component, but leaves us the ability to add / override them in specific use cases.

React Native still has the concept of style inheritance, but limited to text subtrees. In this case, the second part will be both bold and red.

<Text style={{fontWeight: 'bold'}}> + I am bold + <Text style={{color: 'red'}}> + and red + </Text> +</Text>

We believe that this more constrained way to style text will yield better apps:

  • (Developer) React components are designed with strong isolation in mind: You should be able to drop a component anywhere in your application, trusting that as long as the props are the same, it will look and behave the same way. Text properties that could inherit from outside of the props would break this isolation.

  • (Implementor) The implementation of React Native is also simplified. We do not need to have a fontFamily field on every single element, and we do not need to potentially traverse the tree up to the root every time we display a text node. The style inheritance is only encoded inside of the native Text component and doesn't leak to other components or the system itself.

You can edit the content above on GitHub and send us a pull request!

Examples #

IOS #

Edit on GitHub
'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + Image, + StyleSheet, + Text, + View, + LayoutAnimation, +} = ReactNative; + +class Entity extends React.Component { + render() { + return ( + <Text style={{fontWeight: '500', color: '#527fe4'}}> + {this.props.children} + </Text> + ); + } +} + +class AttributeToggler extends React.Component { + state = {fontWeight: 'bold', fontSize: 15}; + + toggleWeight = () => { + this.setState({ + fontWeight: this.state.fontWeight === 'bold' ? 'normal' : 'bold' + }); + }; + + increaseSize = () => { + this.setState({ + fontSize: this.state.fontSize + 1 + }); + }; + + render() { + var curStyle = {fontWeight: this.state.fontWeight, fontSize: this.state.fontSize}; + return ( + <View> + <Text style={curStyle}> + Tap the controls below to change attributes. + </Text> + <Text> + <Text>See how it will even work on <Text style={curStyle}>this nested text</Text></Text> + </Text> + <Text + style={{backgroundColor: '#ffaaaa', marginTop: 5}} + onPress={this.toggleWeight}> + Toggle Weight + </Text> + <Text + style={{backgroundColor: '#aaaaff', marginTop: 5}} + onPress={this.increaseSize}> + Increase Size + </Text> + </View> + ); + } +} + +var AdjustingFontSize = React.createClass({ + getInitialState: function() { + return {dynamicText:'', shouldRender: true,}; + }, + reset: function() { + LayoutAnimation.easeInEaseOut(); + this.setState({ + shouldRender: false, + }); + setTimeout(()=>{ + LayoutAnimation.easeInEaseOut(); + this.setState({ + dynamicText: '', + shouldRender: true, + }); + }, 300); + }, + addText: function() { + this.setState({ + dynamicText: this.state.dynamicText + (Math.floor((Math.random() * 10) % 2) ? ' foo' : ' bar'), + }); + }, + removeText: function() { + this.setState({ + dynamicText: this.state.dynamicText.slice(0, this.state.dynamicText.length - 4), + }); + }, + render: function() { + + if (!this.state.shouldRender) { + return (<View/>); + } + return ( + <View> + <Text lineBreakMode="tail" numberOfLines={1} style={{fontSize: 36, marginVertical:6}}> + Truncated text is baaaaad. + </Text> + <Text numberOfLines={1} adjustsFontSizeToFit={true} style={{fontSize: 40, marginVertical:6}}> + Shrinking to fit available space is much better! + </Text> + + <Text adjustsFontSizeToFit={true} numberOfLines={1} style={{fontSize:30, marginVertical:6}}> + {'Add text to me to watch me shrink!' + ' ' + this.state.dynamicText} + </Text> + + <Text adjustsFontSizeToFit={true} numberOfLines={4} style={{fontSize:20, marginVertical:6}}> + {'Multiline text component shrinking is supported, watch as this reeeeaaaally loooooong teeeeeeext grooooows and then shriiiinks as you add text to me! ioahsdia soady auydoa aoisyd aosdy ' + ' ' + this.state.dynamicText} + </Text> + + <Text adjustsFontSizeToFit={true} numberOfLines={1} style={{marginVertical:6}}> + <Text style={{fontSize:14}}> + {'Differently sized nested elements will shrink together. '} + </Text> + <Text style={{fontSize:20}}> + {'LARGE TEXT! ' + this.state.dynamicText} + </Text> + </Text> + + <View style={{flexDirection:'row', justifyContent:'space-around', marginTop: 5, marginVertical:6}}> + <Text + style={{backgroundColor: '#ffaaaa'}} + onPress={this.reset}> + Reset + </Text> + <Text + style={{backgroundColor: '#aaaaff'}} + onPress={this.removeText}> + Remove Text + </Text> + <Text + style={{backgroundColor: '#aaffaa'}} + onPress={this.addText}> + Add Text + </Text> + </View> + </View> + ); + } +}); + +exports.title = '<Text>'; +exports.description = 'Base component for rendering styled text.'; +exports.displayName = 'TextExample'; +exports.examples = [ +{ + title: 'Wrap', + render: function() { + return ( + <Text> + The text should wrap if it goes on multiple lines. See, this is going to + the next line. + </Text> + ); + }, +}, { + title: 'Padding', + render: function() { + return ( + <Text style={{padding: 10}}> + This text is indented by 10px padding on all sides. + </Text> + ); + }, +}, { + title: 'Font Family', + render: function() { + return ( + <View> + <Text style={{fontFamily: 'Cochin'}}> + Cochin + </Text> + <Text style={{fontFamily: 'Cochin', fontWeight: 'bold'}}> + Cochin bold + </Text> + <Text style={{fontFamily: 'Helvetica'}}> + Helvetica + </Text> + <Text style={{fontFamily: 'Helvetica', fontWeight: 'bold'}}> + Helvetica bold + </Text> + <Text style={{fontFamily: 'Verdana'}}> + Verdana + </Text> + <Text style={{fontFamily: 'Verdana', fontWeight: 'bold'}}> + Verdana bold + </Text> + </View> + ); + }, +}, { + title: 'Font Size', + render: function() { + return ( + <View> + <Text style={{fontSize: 23}}> + Size 23 + </Text> + <Text style={{fontSize: 8}}> + Size 8 + </Text> + </View> + ); + }, +}, { + title: 'Color', + render: function() { + return ( + <View> + <Text style={{color: 'red'}}> + Red color + </Text> + <Text style={{color: 'blue'}}> + Blue color + </Text> + </View> + ); + }, +}, { + title: 'Font Weight', + render: function() { + return ( + <View> + <Text style={{fontSize: 20, fontWeight: '100'}}> + Move fast and be ultralight + </Text> + <Text style={{fontSize: 20, fontWeight: '200'}}> + Move fast and be light + </Text> + <Text style={{fontSize: 20, fontWeight: 'normal'}}> + Move fast and be normal + </Text> + <Text style={{fontSize: 20, fontWeight: 'bold'}}> + Move fast and be bold + </Text> + <Text style={{fontSize: 20, fontWeight: '900'}}> + Move fast and be ultrabold + </Text> + </View> + ); + }, +}, { + title: 'Font Style', + render: function() { + return ( + <View> + <Text style={{fontStyle: 'normal'}}> + Normal text + </Text> + <Text style={{fontStyle: 'italic'}}> + Italic text + </Text> + </View> + ); + }, +}, { + title: 'Text Decoration', + render: function() { + return ( + <View> + <Text style={{textDecorationLine: 'underline', textDecorationStyle: 'solid'}}> + Solid underline + </Text> + <Text style={{textDecorationLine: 'underline', textDecorationStyle: 'double', textDecorationColor: '#ff0000'}}> + Double underline with custom color + </Text> + <Text style={{textDecorationLine: 'underline', textDecorationStyle: 'dashed', textDecorationColor: '#9CDC40'}}> + Dashed underline with custom color + </Text> + <Text style={{textDecorationLine: 'underline', textDecorationStyle: 'dotted', textDecorationColor: 'blue'}}> + Dotted underline with custom color + </Text> + <Text style={{textDecorationLine: 'none'}}> + None textDecoration + </Text> + <Text style={{textDecorationLine: 'line-through', textDecorationStyle: 'solid'}}> + Solid line-through + </Text> + <Text style={{textDecorationLine: 'line-through', textDecorationStyle: 'double', textDecorationColor: '#ff0000'}}> + Double line-through with custom color + </Text> + <Text style={{textDecorationLine: 'line-through', textDecorationStyle: 'dashed', textDecorationColor: '#9CDC40'}}> + Dashed line-through with custom color + </Text> + <Text style={{textDecorationLine: 'line-through', textDecorationStyle: 'dotted', textDecorationColor: 'blue'}}> + Dotted line-through with custom color + </Text> + <Text style={{textDecorationLine: 'underline line-through'}}> + Both underline and line-through + </Text> + </View> + ); + }, +}, { + title: 'Nested', + description: 'Nested text components will inherit the styles of their ' + + 'parents (only backgroundColor is inherited from non-Text parents). ' + + '<Text> only supports other <Text> and raw text (strings) as children.', + render: function() { + return ( + <View> + <Text> + (Normal text, + <Text style={{fontWeight: 'bold'}}> + (and bold + <Text style={{fontSize: 11, color: '#527fe4'}}> + (and tiny inherited bold blue) + </Text> + ) + </Text> + ) + </Text> + <Text style={{opacity:0.7}}> + (opacity + <Text> + (is inherited + <Text style={{opacity:0.7}}> + (and accumulated + <Text style={{backgroundColor:'#ffaaaa'}}> + (and also applies to the background) + </Text> + ) + </Text> + ) + </Text> + ) + </Text> + <Text style={{fontSize: 12}}> + <Entity>Entity Name</Entity> + </Text> + </View> + ); + }, +}, { + title: 'Text Align', + render: function() { + return ( + <View> + <Text> + auto (default) - english LTR + </Text> + <Text> + أحب اللغة العربية auto (default) - arabic RTL + </Text> + <Text style={{textAlign: 'left'}}> + left left left left left left left left left left left left left left left + </Text> + <Text style={{textAlign: 'center'}}> + center center center center center center center center center center center + </Text> + <Text style={{textAlign: 'right'}}> + right right right right right right right right right right right right right + </Text> + <Text style={{textAlign: 'justify'}}> + justify: this text component{"'"}s contents are laid out with "textAlign: justify" + and as you can see all of the lines except the last one span the + available width of the parent container. + </Text> + </View> + ); + }, +}, { + title: 'Letter Spacing', + render: function() { + return ( + <View> + <Text style={{letterSpacing: 0}}> + letterSpacing = 0 + </Text> + <Text style={{letterSpacing: 2, marginTop: 5}}> + letterSpacing = 2 + </Text> + <Text style={{letterSpacing: 9, marginTop: 5}}> + letterSpacing = 9 + </Text> + <Text style={{letterSpacing: -1, marginTop: 5}}> + letterSpacing = -1 + </Text> + </View> + ); + }, +}, { + title: 'Spaces', + render: function() { + return ( + <Text> + A {'generated'} {' '} {'string'} and some &nbsp;&nbsp;&nbsp; spaces + </Text> + ); + }, +}, { + title: 'Line Height', + render: function() { + return ( + <Text> + <Text style={{lineHeight: 35}}> + A lot of space between the lines of this long passage that should + wrap once. + </Text> + </Text> + ); + }, +}, { + title: 'Empty Text', + description: 'It\'s ok to have Text with zero or null children.', + render: function() { + return ( + <Text /> + ); + }, +}, { + title: 'Toggling Attributes', + render: function(): React.Element<any> { + return <AttributeToggler />; + }, +}, { + title: 'backgroundColor attribute', + description: 'backgroundColor is inherited from all types of views.', + render: function() { + return ( + <Text style={{backgroundColor: 'yellow'}}> + Yellow container background, + <Text style={{backgroundColor: '#ffaaaa'}}> + {' '}red background, + <Text style={{backgroundColor: '#aaaaff'}}> + {' '}blue background, + <Text> + {' '}inherited blue background, + <Text style={{backgroundColor: '#aaffaa'}}> + {' '}nested green background. + </Text> + </Text> + </Text> + </Text> + </Text> + ); + }, +}, { + title: 'numberOfLines attribute', + render: function() { + return ( + <View> + <Text numberOfLines={1}> + Maximum of one line, no matter how much I write here. If I keep writing, it{"'"}ll just truncate after one line. + </Text> + <Text numberOfLines={2} style={{marginTop: 20}}> + Maximum of two lines, no matter how much I write here. If I keep writing, it{"'"}ll just truncate after two lines. + </Text> + <Text style={{marginTop: 20}}> + No maximum lines specified, no matter how much I write here. If I keep writing, it{"'"}ll just keep going and going. + </Text> + </View> + ); + }, +}, { + title: 'Text highlighting (tap the link to see highlight)', + render: function() { + return ( + <View> + <Text>Lorem ipsum dolor sit amet, <Text suppressHighlighting={false} style={{backgroundColor: 'white', textDecorationLine: 'underline', color: 'blue'}} onPress={() => null}>consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud</Text> exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</Text> + </View> + ); + }, +}, { + title: 'allowFontScaling attribute', + render: function() { + return ( + <View> + <Text> + By default, text will respect Text Size accessibility setting on iOS. + It means that all font sizes will be increased or descreased depending on the value of Text Size setting in + {" "}<Text style={{fontWeight: 'bold'}}>Settings.app - Display & Brightness - Text Size</Text> + </Text> + <Text style={{marginTop: 10}}> + You can disable scaling for your Text component by passing {"\""}allowFontScaling={"{"}false{"}\""} prop. + </Text> + <Text allowFontScaling={false} style={{marginTop: 20}}> + This text will not scale. + </Text> + </View> + ); + }, +}, { + title: 'Inline views', + render: function() { + return ( + <View> + <Text> + This text contains an inline blue view <View style={{width: 25, height: 25, backgroundColor: 'steelblue'}} /> and + an inline image <Image source={require('./flux.png')} style={{width: 30, height: 11, resizeMode: 'cover'}}/>. Neat, huh? + </Text> + </View> + ); + }, +}, { + title: 'Text shadow', + render: function() { + return ( + <View> + <Text style={{fontSize: 20, textShadowOffset: {width: 2, height: 2}, textShadowRadius: 1, textShadowColor: '#00cccc'}}> + Demo text shadow + </Text> + </View> + ); + }, +}, { + title: 'Ellipsize mode', + render: function() { + return ( + <View> + <Text numberOfLines={1}> + This very long text should be truncated with dots in the end. + </Text> + <Text ellipsizeMode="middle" numberOfLines={1}> + This very long text should be truncated with dots in the middle. + </Text> + <Text ellipsizeMode="head" numberOfLines={1}> + This very long text should be truncated with dots in the beginning. + </Text> + <Text ellipsizeMode="clip" numberOfLines={1}> + This very looooooooooooooooooooooooooooong text should be clipped. + </Text> + </View> + ); + }, +}, { + title: 'Font variants', + render: function() { + return ( + <View> + <Text style={{fontVariant: ['small-caps']}}> + Small Caps{'\n'} + </Text> + <Text style={{fontFamily: 'Hoefler Text', fontVariant: ['oldstyle-nums']}}> + Old Style nums 0123456789{'\n'} + </Text> + <Text style={{fontFamily: 'Hoefler Text', fontVariant: ['lining-nums']}}> + Lining nums 0123456789{'\n'} + </Text> + <Text style={{fontVariant: ['tabular-nums']}}> + Tabular nums{'\n'} + 1111{'\n'} + 2222{'\n'} + </Text> + <Text style={{fontVariant: ['proportional-nums']}}> + Proportional nums{'\n'} + 1111{'\n'} + 2222{'\n'} + </Text> + </View> + ); + }, +}, { + title: 'Dynamic Font Size Adjustment', + render: function(): React.Element<any> { + return <AdjustingFontSize />; + }, +}];

ANDROID #

Edit on GitHub
'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + Image, + StyleSheet, + Text, + View, +} = ReactNative; +var UIExplorerBlock = require('./UIExplorerBlock'); +var UIExplorerPage = require('./UIExplorerPage'); + +class Entity extends React.Component { + render() { + return ( + <Text style={{fontWeight: 'bold', color: '#527fe4'}}> + {this.props.children} + </Text> + ); + } +} + +class AttributeToggler extends React.Component { + state = {fontWeight: 'bold', fontSize: 15}; + + toggleWeight = () => { + this.setState({ + fontWeight: this.state.fontWeight === 'bold' ? 'normal' : 'bold' + }); + }; + + increaseSize = () => { + this.setState({ + fontSize: this.state.fontSize + 1 + }); + }; + + render() { + var curStyle = {fontWeight: this.state.fontWeight, fontSize: this.state.fontSize}; + return ( + <View> + <Text style={curStyle}> + Tap the controls below to change attributes. + </Text> + <Text> + <Text>See how it will even work on <Text style={curStyle}>this nested text</Text></Text> + </Text> + <Text> + <Text onPress={this.toggleWeight}>Toggle Weight</Text> + {' (with highlight onPress)'} + </Text> + <Text onPress={this.increaseSize} suppressHighlighting={true}> + Increase Size (suppressHighlighting true) + </Text> + </View> + ); + } +} + +class TextExample extends React.Component { + static title = '<Text>'; + static description = 'Base component for rendering styled text.'; + + render() { + return ( + <UIExplorerPage title="<Text>"> + <UIExplorerBlock title="Wrap"> + <Text> + The text should wrap if it goes on multiple lines. + See, this is going to the next line. + </Text> + </UIExplorerBlock> + <UIExplorerBlock title="Padding"> + <Text style={{padding: 10}}> + This text is indented by 10px padding on all sides. + </Text> + </UIExplorerBlock> + <UIExplorerBlock title="Font Family"> + <Text style={{fontFamily: 'sans-serif'}}> + Sans-Serif + </Text> + <Text style={{fontFamily: 'sans-serif', fontWeight: 'bold'}}> + Sans-Serif Bold + </Text> + <Text style={{fontFamily: 'serif'}}> + Serif + </Text> + <Text style={{fontFamily: 'serif', fontWeight: 'bold'}}> + Serif Bold + </Text> + <Text style={{fontFamily: 'monospace'}}> + Monospace + </Text> + <Text style={{fontFamily: 'monospace', fontWeight: 'bold'}}> + Monospace Bold (After 5.0) + </Text> + </UIExplorerBlock> + <UIExplorerBlock title="Android Material Design fonts"> + <View style={{flexDirection: 'row', alignItems: 'flex-start'}}> + <View style={{flex: 1}}> + <Text style={{fontFamily: 'sans-serif'}}> + Roboto Regular + </Text> + <Text style={{fontFamily: 'sans-serif', fontStyle: 'italic'}}> + Roboto Italic + </Text> + <Text style={{fontFamily: 'sans-serif', fontWeight: 'bold'}}> + Roboto Bold + </Text> + <Text style={{fontFamily: 'sans-serif', fontStyle: 'italic', fontWeight: 'bold'}}> + Roboto Bold Italic + </Text> + <Text style={{fontFamily: 'sans-serif-light'}}> + Roboto Light + </Text> + <Text style={{fontFamily: 'sans-serif-light', fontStyle: 'italic'}}> + Roboto Light Italic + </Text> + <Text style={{fontFamily: 'sans-serif-thin'}}> + Roboto Thin (After 4.2) + </Text> + <Text style={{fontFamily: 'sans-serif-thin', fontStyle: 'italic'}}> + Roboto Thin Italic (After 4.2) + </Text> + <Text style={{fontFamily: 'sans-serif-condensed'}}> + Roboto Condensed + </Text> + <Text style={{fontFamily: 'sans-serif-condensed', fontStyle: 'italic'}}> + Roboto Condensed Italic + </Text> + <Text style={{fontFamily: 'sans-serif-condensed', fontWeight: 'bold'}}> + Roboto Condensed Bold + </Text> + <Text style={{ + fontFamily: 'sans-serif-condensed', + fontStyle: 'italic', + fontWeight: 'bold'}}> + Roboto Condensed Bold Italic + </Text> + <Text style={{fontFamily: 'sans-serif-medium'}}> + Roboto Medium (After 5.0) + </Text> + <Text style={{fontFamily: 'sans-serif-medium', fontStyle: 'italic'}}> + Roboto Medium Italic (After 5.0) + </Text> + </View> + </View> + </UIExplorerBlock> + <UIExplorerBlock title="Custom Fonts"> + <View style={{flexDirection: 'row', alignItems: 'flex-start'}}> + <View style={{flex: 1}}> + <Text style={{fontFamily: 'notoserif'}}> + NotoSerif Regular + </Text> + <Text style={{fontFamily: 'notoserif', fontStyle: 'italic', fontWeight: 'bold'}}> + NotoSerif Bold Italic + </Text> + <Text style={{fontFamily: 'notoserif', fontStyle: 'italic'}}> + NotoSerif Italic (Missing Font file) + </Text> + </View> + </View> + </UIExplorerBlock> + + <UIExplorerBlock title="Font Size"> + <Text style={{fontSize: 23}}> + Size 23 + </Text> + <Text style={{fontSize: 8}}> + Size 8 + </Text> + </UIExplorerBlock> + <UIExplorerBlock title="Color"> + <Text style={{color: 'red'}}> + Red color + </Text> + <Text style={{color: 'blue'}}> + Blue color + </Text> + </UIExplorerBlock> + <UIExplorerBlock title="Font Weight"> + <Text style={{fontWeight: 'bold'}}> + Move fast and be bold + </Text> + <Text style={{fontWeight: 'normal'}}> + Move fast and be bold + </Text> + </UIExplorerBlock> + <UIExplorerBlock title="Font Style"> + <Text style={{fontStyle: 'italic'}}> + Move fast and be bold + </Text> + <Text style={{fontStyle: 'normal'}}> + Move fast and be bold + </Text> + </UIExplorerBlock> + <UIExplorerBlock title="Font Style and Weight"> + <Text style={{fontStyle: 'italic', fontWeight: 'bold'}}> + Move fast and be bold + </Text> + </UIExplorerBlock> + <UIExplorerBlock title="Text Decoration"> + <Text style={{textDecorationLine: 'underline'}}> + Solid underline + </Text> + <Text style={{textDecorationLine: 'none'}}> + None textDecoration + </Text> + <Text style={{textDecorationLine: 'line-through', textDecorationStyle: 'solid'}}> + Solid line-through + </Text> + <Text style={{textDecorationLine: 'underline line-through'}}> + Both underline and line-through + </Text> + <Text> + Mixed text with <Text style={{textDecorationLine: 'underline'}}>underline</Text> and <Text style={{textDecorationLine: 'line-through'}}>line-through</Text> text nodes + </Text> + </UIExplorerBlock> + <UIExplorerBlock title="Nested"> + <Text onPress={() => console.log('1st')}> + (Normal text, + <Text style={{fontWeight: 'bold'}} onPress={() => console.log('2nd')}> + (and bold + <Text style={{fontStyle: 'italic', fontSize: 11, color: '#527fe4'}} onPress={() => console.log('3rd')}> + (and tiny bold italic blue + <Text style={{fontWeight: 'normal', fontStyle: 'normal'}} onPress={() => console.log('4th')}> + (and tiny normal blue) + </Text> + ) + </Text> + ) + </Text> + ) + </Text> + <Text style={{fontFamily: 'serif'}} onPress={() => console.log('1st')}> + (Serif + <Text style={{fontStyle: 'italic', fontWeight: 'bold'}} onPress={() => console.log('2nd')}> + (Serif Bold Italic + <Text + style={{fontFamily: 'monospace', fontStyle: 'normal', fontWeight: 'normal'}} + onPress={() => console.log('3rd')}> + (Monospace Normal + <Text + style={{fontFamily: 'sans-serif', fontWeight: 'bold'}} + onPress={() => console.log('4th')}> + (Sans-Serif Bold + <Text style={{fontWeight: 'normal'}} onPress={() => console.log('5th')}> + (and Sans-Serif Normal) + </Text> + ) + </Text> + ) + </Text> + ) + </Text> + ) + </Text> + <Text style={{fontSize: 12}}> + <Entity>Entity Name</Entity> + </Text> + </UIExplorerBlock> + <UIExplorerBlock title="Text Align"> + <Text> + auto (default) - english LTR + </Text> + <Text> + أحب اللغة العربية auto (default) - arabic RTL + </Text> + <Text style={{textAlign: 'left'}}> + left left left left left left left left left left left left left left left + </Text> + <Text style={{textAlign: 'center'}}> + center center center center center center center center center center center + </Text> + <Text style={{textAlign: 'right'}}> + right right right right right right right right right right right right right + </Text> + </UIExplorerBlock> + <UIExplorerBlock title="Unicode"> + <View> + <View style={{flexDirection: 'row'}}> + <Text style={{backgroundColor: 'red'}}> + 星际争霸是世界上最好的游戏。 + </Text> + </View> + <View> + <Text style={{backgroundColor: 'red'}}> + 星际争霸是世界上最好的游戏。 + </Text> + </View> + <View style={{alignItems: 'center'}}> + <Text style={{backgroundColor: 'red'}}> + 星际争霸是世界上最好的游戏。 + </Text> + </View> + <View> + <Text style={{backgroundColor: 'red'}}> + 星际争霸是世界上最好的游戏。星际争霸是世界上最好的游戏。星际争霸是世界上最好的游戏。星际争霸是世界上最好的游戏。 + </Text> + </View> + </View> + </UIExplorerBlock> + <UIExplorerBlock title="Spaces"> + <Text> + A {'generated'} {' '} {'string'} and some &nbsp;&nbsp;&nbsp; spaces + </Text> + </UIExplorerBlock> + <UIExplorerBlock title="Line Height"> + <Text style={{lineHeight: 35}}> + Holisticly formulate inexpensive ideas before best-of-breed benefits. <Text style={{fontSize: 20}}>Continually</Text> expedite magnetic potentialities rather than client-focused interfaces. + </Text> + </UIExplorerBlock> + <UIExplorerBlock title="Empty Text"> + <Text /> + </UIExplorerBlock> + <UIExplorerBlock title="Toggling Attributes"> + <AttributeToggler /> + </UIExplorerBlock> + <UIExplorerBlock title="backgroundColor attribute"> + <Text style={{backgroundColor: '#ffaaaa'}}> + Red background, + <Text style={{backgroundColor: '#aaaaff'}}> + {' '}blue background, + <Text> + {' '}inherited blue background, + <Text style={{backgroundColor: '#aaffaa'}}> + {' '}nested green background. + </Text> + </Text> + </Text> + </Text> + <Text style={{backgroundColor: 'rgba(100, 100, 100, 0.3)'}}> + Same alpha as background, + <Text> + Inherited alpha from background, + <Text style={{backgroundColor: 'rgba(100, 100, 100, 0.3)'}}> + Reapply alpha + </Text> + </Text> + </Text> + </UIExplorerBlock> + <UIExplorerBlock title="containerBackgroundColor attribute"> + <View style={{flexDirection: 'row', height: 85}}> + <View style={{backgroundColor: '#ffaaaa', width: 150}} /> + <View style={{backgroundColor: '#aaaaff', width: 150}} /> + </View> + <Text style={[styles.backgroundColorText, {top: -80}]}> + Default containerBackgroundColor (inherited) + backgroundColor wash + </Text> + <Text style={[styles.backgroundColorText, {top: -70, backgroundColor: 'transparent'}]}> + {"containerBackgroundColor: 'transparent' + backgroundColor wash"} + </Text> + </UIExplorerBlock> + <UIExplorerBlock title="numberOfLines attribute"> + <Text numberOfLines={1}> + Maximum of one line no matter now much I write here. If I keep writing it{"'"}ll just truncate after one line + </Text> + <Text numberOfLines={2} style={{marginTop: 20}}> + Maximum of two lines no matter now much I write here. If I keep writing it{"'"}ll just truncate after two lines + </Text> + <Text style={{marginTop: 20}}> + No maximum lines specified no matter now much I write here. If I keep writing it{"'"}ll just keep going and going + </Text> + </UIExplorerBlock> + <UIExplorerBlock title="selectable attribute"> + <Text selectable={true}> + This text is selectable if you click-and-hold, and will offer the native Android selection menus. + </Text> + </UIExplorerBlock> + <UIExplorerBlock title="Inline images"> + <Text> + This text contains an inline image <Image source={require('./flux.png')}/>. Neat, huh? + </Text> + </UIExplorerBlock> + <UIExplorerBlock title="Text shadow"> + <Text style={{fontSize: 20, textShadowOffset: {width: 2, height: 2}, textShadowRadius: 1, textShadowColor: '#00cccc'}}> + Demo text shadow + </Text> + </UIExplorerBlock> + <UIExplorerBlock title="Ellipsize mode"> + <Text numberOfLines={1}> + This very long text should be truncated with dots in the end. + </Text> + <Text ellipsizeMode="middle" numberOfLines={1}> + This very long text should be truncated with dots in the middle. + </Text> + <Text ellipsizeMode="head" numberOfLines={1}> + This very long text should be truncated with dots in the beginning. + </Text> + </UIExplorerBlock> + </UIExplorerPage> + ); + } +} + +var styles = StyleSheet.create({ + backgroundColorText: { + left: 5, + backgroundColor: 'rgba(100, 100, 100, 0.3)' + }, +}); + +module.exports = TextExample;
\ No newline at end of file diff --git a/releases/0.37/docs/textinput.html b/releases/0.37/docs/textinput.html new file mode 100644 index 00000000000..5f615583e2f --- /dev/null +++ b/releases/0.37/docs/textinput.html @@ -0,0 +1,1689 @@ +TextInput

TextInput #

A foundational component for inputting text into the app via a +keyboard. Props provide configurability for several features, such as +auto-correction, auto-capitalization, placeholder text, and different keyboard +types, such as a numeric keypad.

The simplest use case is to plop down a TextInput and subscribe to the +onChangeText events to read the user input. There are also other events, +such as onSubmitEditing and onFocus that can be subscribed to. A simple +example:

import React, { Component } from 'react'; +import { AppRegistry, TextInput } from 'react-native'; + +class UselessTextInput extends Component { + constructor(props) { + super(props); + this.state = { text: 'Useless Placeholder' }; + } + + render() { + return ( + <TextInput + style={{height: 40, borderColor: 'gray', borderWidth: 1}} + onChangeText={(text) => this.setState({text})} + value={this.state.text} + /> + ); + } +} + +// App registration and rendering +AppRegistry.registerComponent('AwesomeProject', () => UselessTextInput);

Note that some props are only available with multiline={true/false}. +Additionally, border styles that apply to only one side of the element +(e.g., borderBottomColor, borderLeftWidth, etc.) will not be applied if +multiline=false. To achieve the same effect, you can wrap your TextInput +in a View:

import React, { Component } from 'react'; +import { AppRegistry, View, TextInput } from 'react-native'; + +class UselessTextInput extends Component { + render() { + return ( + <TextInput + {...this.props} // Inherit any props passed to it; e.g., multiline, numberOfLines below + editable = {true} + maxLength = {40} + /> + ); + } +} + +class UselessTextInputMultiline extends Component { + constructor(props) { + super(props); + this.state = { + text: 'Useless Multiline Placeholder', + }; + } + + // If you type something in the text box that is a color, the background will change to that + // color. + render() { + return ( + <View style={{ + backgroundColor: this.state.text, + borderBottomColor: '#000000', + borderBottomWidth: 1 }} + > + <UselessTextInput + multiline = {true} + numberOfLines = {4} + onChangeText={(text) => this.setState({text})} + value={this.state.text} + /> + </View> + ); + } +} + +// App registration and rendering +AppRegistry.registerComponent( + 'AwesomeProject', + () => UselessTextInputMultiline +);

TextInput has by default a border at the bottom of its view. This border +has its padding set by the background image provided by the system, and it +cannot be changed. Solutions to avoid this is to either not set height +explicitly, case in which the system will take care of displaying the border +in the correct position, or to not display the border by setting +underlineColorAndroid to transparent.

Props #

autoCapitalize enum('none', 'sentences', 'words', 'characters') #

Can tell TextInput to automatically capitalize certain characters.

  • characters: all characters.
  • words: first letter of each word.
  • sentences: first letter of each sentence (default).
  • none: don't auto capitalize anything.

autoCorrect bool #

If false, disables auto-correct. The default value is true.

autoFocus bool #

If true, focuses the input on componentDidMount. +The default value is false.

blurOnSubmit bool #

If true, the text field will blur when submitted. +The default value is true for single-line fields and false for +multiline fields. Note that for multiline fields, setting blurOnSubmit +to true means that pressing return will blur the field and trigger the +onSubmitEditing event instead of inserting a newline into the field.

defaultValue node #

Provides an initial value that will change when the user starts typing. +Useful for simple use-cases where you do not want to deal with listening +to events and updating the value prop to keep the controlled state in sync.

editable bool #

If false, text is not editable. The default value is true.

keyboardType enum('default', 'email-address', 'numeric', 'phone-pad', 'ascii-capable', 'numbers-and-punctuation', 'url', 'number-pad', 'name-phone-pad', 'decimal-pad', 'twitter', 'web-search') #

Determines which keyboard to open, e.g.numeric.

The following values work across platforms:

  • default
  • numeric
  • email-address
  • phone-pad

maxLength number #

Limits the maximum number of characters that can be entered. Use this +instead of implementing the logic in JS to avoid flicker.

multiline bool #

If true, the text input can be multiple lines. +The default value is false.

onBlur function #

Callback that is called when the text input is blurred.

onChange function #

Callback that is called when the text input's text changes.

onChangeText function #

Callback that is called when the text input's text changes. +Changed text is passed as an argument to the callback handler.

onContentSizeChange function #

Callback that is called when the text input's content size changes. +This will be called with +{ nativeEvent: { contentSize: { width, height } } }.

Only called for multiline text inputs.

onEndEditing function #

Callback that is called when text input ends.

onFocus function #

Callback that is called when the text input is focused.

onLayout function #

Invoked on mount and layout changes with {x, y, width, height}.

onSelectionChange function #

Callback that is called when the text input selection is changed.

onSubmitEditing function #

Callback that is called when the text input's submit button is pressed. +Invalid if multiline={true} is specified.

placeholder node #

The string that will be rendered before text input has been entered.

placeholderTextColor color #

The text color of the placeholder string.

returnKeyType enum('done', 'go', 'next', 'search', 'send', 'none', 'previous', 'default', 'emergency-call', 'google', 'join', 'route', 'yahoo') #

Determines how the return key should look. On Android you can also use +returnKeyLabel.

Cross platform

The following values work across platforms:

  • done
  • go
  • next
  • search
  • send

Android Only

The following values work on Android only:

  • none
  • previous

iOS Only

The following values work on iOS only:

  • default
  • emergency-call
  • google
  • join
  • route
  • yahoo

secureTextEntry bool #

If true, the text input obscures the text entered so that sensitive text +like passwords stay secure. The default value is false.

selectTextOnFocus bool #

If true, all text will automatically be selected on focus.

selection {start: number, end: number} #

The start and end of the text input's selection. Set start and end to +the same value to position the cursor.

selectionColor color #

The highlight (and cursor on iOS) color of the text input.

value string #

The value to show for the text input. TextInput is a controlled +component, which means the native value will be forced to match this +value prop if provided. For most uses, this works great, but in some +cases this may cause flickering - one common cause is preventing edits +by keeping value the same. In addition to simply setting the same value, +either set editable={false}, or set/update maxLength to prevent +unwanted edits without flicker.

androidinlineImageLeft string #

If defined, the provided image resource will be rendered on the left.

androidinlineImagePadding number #

Padding between the inline image, if any, and the text input itself.

androidnumberOfLines number #

Sets the number of lines for a TextInput. Use it with multiline set to +true to be able to fill the lines.

androidreturnKeyLabel string #

Sets the return key to the label. Use it instead of returnKeyType.

androidunderlineColorAndroid color #

The color of the TextInput underline.

iosclearButtonMode enum('never', 'while-editing', 'unless-editing', 'always') #

When the clear button should appear on the right side of the text view.

iosclearTextOnFocus bool #

If true, clears the text field automatically when editing begins.

iosdataDetectorTypes enum('phoneNumber', 'link', 'address', 'calendarEvent', 'none', 'all'), [object Object] #

Determines the types of data converted to clickable URLs in the text input. +Only valid if multiline={true} and editable={false}. +By default no data types are detected.

You can provide one type or an array of many types.

Possible values for dataDetectorTypes are:

  • 'phoneNumber'
  • 'link'
  • 'address'
  • 'calendarEvent'
  • 'none'
  • 'all'

iosenablesReturnKeyAutomatically bool #

If true, the keyboard disables the return key when there is no text and +automatically enables it when there is text. The default value is false.

ioskeyboardAppearance enum('default', 'light', 'dark') #

Determines the color of the keyboard.

iosonKeyPress function #

Callback that is called when a key is pressed. +Pressed key value is passed as an argument to the callback handler. +Fires before onChange callbacks.

iosselectionState DocumentSelectionState #

An instance of DocumentSelectionState, this is some state that is responsible for +maintaining selection information for a document.

Some functionality that can be performed with this instance is:

  • blur()
  • focus()
  • update()

You can reference DocumentSelectionState in +vendor/document/selection/DocumentSelectionState.js

Methods #

isFocused(0): #

Returns true if the input is currently focused; false otherwise.

clear(0) #

Removes all text from the TextInput.

You can edit the content above on GitHub and send us a pull request!

Examples #

IOS #

Edit on GitHub
'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + Text, + TextInput, + View, + StyleSheet, +} = ReactNative; + +class WithLabel extends React.Component { + render() { + return ( + <View style={styles.labelContainer}> + <View style={styles.label}> + <Text>{this.props.label}</Text> + </View> + {this.props.children} + </View> + ); + } +} + +class TextEventsExample extends React.Component { + state = { + curText: '<No Event>', + prevText: '<No Event>', + prev2Text: '<No Event>', + prev3Text: '<No Event>', + }; + + updateText = (text) => { + this.setState((state) => { + return { + curText: text, + prevText: state.curText, + prev2Text: state.prevText, + prev3Text: state.prev2Text, + }; + }); + }; + + render() { + return ( + <View> + <TextInput + autoCapitalize="none" + placeholder="Enter text to see events" + autoCorrect={false} + onFocus={() => this.updateText('onFocus')} + onBlur={() => this.updateText('onBlur')} + onChange={(event) => this.updateText( + 'onChange text: ' + event.nativeEvent.text + )} + onEndEditing={(event) => this.updateText( + 'onEndEditing text: ' + event.nativeEvent.text + )} + onSubmitEditing={(event) => this.updateText( + 'onSubmitEditing text: ' + event.nativeEvent.text + )} + onSelectionChange={(event) => this.updateText( + 'onSelectionChange range: ' + + event.nativeEvent.selection.start + ',' + + event.nativeEvent.selection.end + )} + onKeyPress={(event) => { + this.updateText('onKeyPress key: ' + event.nativeEvent.key); + }} + style={styles.default} + /> + <Text style={styles.eventLabel}> + {this.state.curText}{'\n'} + (prev: {this.state.prevText}){'\n'} + (prev2: {this.state.prev2Text}){'\n'} + (prev3: {this.state.prev3Text}) + </Text> + </View> + ); + } +} + +class AutoExpandingTextInput extends React.Component { + state: any; + + constructor(props) { + super(props); + this.state = { + text: 'React Native enables you to build world-class application experiences on native platforms using a consistent developer experience based on JavaScript and React. The focus of React Native is on developer efficiency across all the platforms you care about — learn once, write anywhere. Facebook uses React Native in multiple production apps and will continue investing in React Native.', + height: 0, + }; + } + render() { + return ( + <TextInput + {...this.props} + multiline={true} + onChangeText={(text) => { + this.setState({text}); + }} + onContentSizeChange={(event) => { + this.setState({height: event.nativeEvent.contentSize.height}); + }} + style={[styles.default, {height: Math.max(35, this.state.height)}]} + value={this.state.text} + /> + ); + } +} + +class RewriteExample extends React.Component { + state: any; + + constructor(props) { + super(props); + this.state = {text: ''}; + } + render() { + var limit = 20; + var remainder = limit - this.state.text.length; + var remainderColor = remainder > 5 ? 'blue' : 'red'; + return ( + <View style={styles.rewriteContainer}> + <TextInput + multiline={false} + maxLength={limit} + onChangeText={(text) => { + text = text.replace(/ /g, '_'); + this.setState({text}); + }} + style={styles.default} + value={this.state.text} + /> + <Text style={[styles.remainder, {color: remainderColor}]}> + {remainder} + </Text> + </View> + ); + } +} + +class RewriteExampleInvalidCharacters extends React.Component { + state: any; + + constructor(props) { + super(props); + this.state = {text: ''}; + } + render() { + return ( + <View style={styles.rewriteContainer}> + <TextInput + multiline={false} + onChangeText={(text) => { + this.setState({text: text.replace(/\s/g, '')}); + }} + style={styles.default} + value={this.state.text} + /> + </View> + ); + } +} + +class TokenizedTextExample extends React.Component { + state: any; + + constructor(props) { + super(props); + this.state = {text: 'Hello #World'}; + } + render() { + + //define delimiter + let delimiter = /\s+/; + + //split string + let _text = this.state.text; + let token, index, parts = []; + while (_text) { + delimiter.lastIndex = 0; + token = delimiter.exec(_text); + if (token === null) { + break; + } + index = token.index; + if (token[0].length === 0) { + index = 1; + } + parts.push(_text.substr(0, index)); + parts.push(token[0]); + index = index + token[0].length; + _text = _text.slice(index); + } + parts.push(_text); + + //highlight hashtags + parts = parts.map((text) => { + if (/^#/.test(text)) { + return <Text key={text} style={styles.hashtag}>{text}</Text>; + } else { + return text; + } + }); + + return ( + <View> + <TextInput + multiline={true} + style={styles.multiline} + onChangeText={(text) => { + this.setState({text}); + }}> + <Text>{parts}</Text> + </TextInput> + </View> + ); + } +} + +class BlurOnSubmitExample extends React.Component { + focusNextField = (nextField) => { + this.refs[nextField].focus(); + }; + + render() { + return ( + <View> + <TextInput + ref="1" + style={styles.default} + placeholder="blurOnSubmit = false" + returnKeyType="next" + blurOnSubmit={false} + onSubmitEditing={() => this.focusNextField('2')} + /> + <TextInput + ref="2" + style={styles.default} + keyboardType="email-address" + placeholder="blurOnSubmit = false" + returnKeyType="next" + blurOnSubmit={false} + onSubmitEditing={() => this.focusNextField('3')} + /> + <TextInput + ref="3" + style={styles.default} + keyboardType="url" + placeholder="blurOnSubmit = false" + returnKeyType="next" + blurOnSubmit={false} + onSubmitEditing={() => this.focusNextField('4')} + /> + <TextInput + ref="4" + style={styles.default} + keyboardType="numeric" + placeholder="blurOnSubmit = false" + blurOnSubmit={false} + onSubmitEditing={() => this.focusNextField('5')} + /> + <TextInput + ref="5" + style={styles.default} + keyboardType="numbers-and-punctuation" + placeholder="blurOnSubmit = true" + returnKeyType="done" + /> + </View> + ); + } +} + +type SelectionExampleState = { + selection: { + start: number; + end?: number; + }; + value: string; +}; + +class SelectionExample extends React.Component { + state: SelectionExampleState; + + _textInput: any; + + constructor(props) { + super(props); + this.state = { + selection: {start: 0, end: 0}, + value: props.value + }; + } + + onSelectionChange({nativeEvent: {selection}}) { + this.setState({selection}); + } + + getRandomPosition() { + var length = this.state.value.length; + return Math.round(Math.random() * length); + } + + select(start, end) { + this._textInput.focus(); + this.setState({selection: {start, end}}); + } + + selectRandom() { + var positions = [this.getRandomPosition(), this.getRandomPosition()].sort(); + this.select(...positions); + } + + placeAt(position) { + this.select(position, position); + } + + placeAtRandom() { + this.placeAt(this.getRandomPosition()); + } + + render() { + var length = this.state.value.length; + + return ( + <View> + <TextInput + multiline={this.props.multiline} + onChangeText={(value) => this.setState({value})} + onSelectionChange={this.onSelectionChange.bind(this)} + ref={textInput => (this._textInput = textInput)} + selection={this.state.selection} + style={this.props.style} + value={this.state.value} + /> + <View> + <Text> + selection = {JSON.stringify(this.state.selection)} + </Text> + <Text onPress={this.placeAt.bind(this, 0)}> + Place at Start (0, 0) + </Text> + <Text onPress={this.placeAt.bind(this, length)}> + Place at End ({length}, {length}) + </Text> + <Text onPress={this.placeAtRandom.bind(this)}> + Place at Random + </Text> + <Text onPress={this.select.bind(this, 0, length)}> + Select All + </Text> + <Text onPress={this.selectRandom.bind(this)}> + Select Random + </Text> + </View> + </View> + ); + } +} + +var styles = StyleSheet.create({ + page: { + paddingBottom: 300, + }, + default: { + height: 26, + borderWidth: 0.5, + borderColor: '#0f0f0f', + flex: 1, + fontSize: 13, + padding: 4, + }, + multiline: { + borderWidth: 0.5, + borderColor: '#0f0f0f', + flex: 1, + fontSize: 13, + height: 50, + padding: 4, + marginBottom: 4, + }, + multilineWithFontStyles: { + color: 'blue', + fontWeight: 'bold', + fontSize: 18, + fontFamily: 'Cochin', + height: 60, + }, + multilineChild: { + width: 50, + height: 40, + position: 'absolute', + right: 5, + backgroundColor: 'red', + }, + eventLabel: { + margin: 3, + fontSize: 12, + }, + labelContainer: { + flexDirection: 'row', + marginVertical: 2, + flex: 1, + }, + label: { + width: 115, + alignItems: 'flex-end', + marginRight: 10, + paddingTop: 2, + }, + rewriteContainer: { + flexDirection: 'row', + alignItems: 'center', + }, + remainder: { + textAlign: 'right', + width: 24, + }, + hashtag: { + color: 'blue', + fontWeight: 'bold', + }, +}); + +exports.displayName = (undefined: ?string); +exports.title = '<TextInput>'; +exports.description = 'Single and multi-line text inputs.'; +exports.examples = [ + { + title: 'Auto-focus', + render: function() { + return ( + <TextInput + autoFocus={true} + style={styles.default} + accessibilityLabel="I am the accessibility label for text input" + /> + ); + } + }, + { + title: "Live Re-Write (<sp> -> '_') + maxLength", + render: function() { + return <RewriteExample />; + } + }, + { + title: 'Live Re-Write (no spaces allowed)', + render: function() { + return <RewriteExampleInvalidCharacters />; + } + }, + { + title: 'Auto-capitalize', + render: function() { + return ( + <View> + <WithLabel label="none"> + <TextInput + autoCapitalize="none" + style={styles.default} + /> + </WithLabel> + <WithLabel label="sentences"> + <TextInput + autoCapitalize="sentences" + style={styles.default} + /> + </WithLabel> + <WithLabel label="words"> + <TextInput + autoCapitalize="words" + style={styles.default} + /> + </WithLabel> + <WithLabel label="characters"> + <TextInput + autoCapitalize="characters" + style={styles.default} + /> + </WithLabel> + </View> + ); + } + }, + { + title: 'Auto-correct', + render: function() { + return ( + <View> + <WithLabel label="true"> + <TextInput autoCorrect={true} style={styles.default} /> + </WithLabel> + <WithLabel label="false"> + <TextInput autoCorrect={false} style={styles.default} /> + </WithLabel> + </View> + ); + } + }, + { + title: 'Keyboard types', + render: function() { + var keyboardTypes = [ + 'default', + 'ascii-capable', + 'numbers-and-punctuation', + 'url', + 'number-pad', + 'phone-pad', + 'name-phone-pad', + 'email-address', + 'decimal-pad', + 'twitter', + 'web-search', + 'numeric', + ]; + var examples = keyboardTypes.map((type) => { + return ( + <WithLabel key={type} label={type}> + <TextInput + keyboardType={type} + style={styles.default} + /> + </WithLabel> + ); + }); + return <View>{examples}</View>; + } + }, + { + title: 'Keyboard appearance', + render: function() { + var keyboardAppearance = [ + 'default', + 'light', + 'dark', + ]; + var examples = keyboardAppearance.map((type) => { + return ( + <WithLabel key={type} label={type}> + <TextInput + keyboardAppearance={type} + style={styles.default} + /> + </WithLabel> + ); + }); + return <View>{examples}</View>; + } + }, + { + title: 'Return key types', + render: function() { + var returnKeyTypes = [ + 'default', + 'go', + 'google', + 'join', + 'next', + 'route', + 'search', + 'send', + 'yahoo', + 'done', + 'emergency-call', + ]; + var examples = returnKeyTypes.map((type) => { + return ( + <WithLabel key={type} label={type}> + <TextInput + returnKeyType={type} + style={styles.default} + /> + </WithLabel> + ); + }); + return <View>{examples}</View>; + } + }, + { + title: 'Enable return key automatically', + render: function() { + return ( + <View> + <WithLabel label="true"> + <TextInput enablesReturnKeyAutomatically={true} style={styles.default} /> + </WithLabel> + </View> + ); + } + }, + { + title: 'Secure text entry', + render: function() { + return ( + <View> + <WithLabel label="true"> + <TextInput secureTextEntry={true} style={styles.default} defaultValue="abc" /> + </WithLabel> + </View> + ); + } + }, + { + title: 'Event handling', + render: function(): React.Element<any> { return <TextEventsExample />; }, + }, + { + title: 'Colored input text', + render: function() { + return ( + <View> + <TextInput + style={[styles.default, {color: 'blue'}]} + defaultValue="Blue" + /> + <TextInput + style={[styles.default, {color: 'green'}]} + defaultValue="Green" + /> + </View> + ); + } + }, + { + title: 'Colored highlight/cursor for text input', + render: function() { + return ( + <View> + <TextInput + style={styles.default} + selectionColor={"green"} + defaultValue="Highlight me" + /> + <TextInput + style={styles.default} + selectionColor={"rgba(86, 76, 205, 1)"} + defaultValue="Highlight me" + /> + </View> + ); + } + }, + { + title: 'Clear button mode', + render: function () { + return ( + <View> + <WithLabel label="never"> + <TextInput + style={styles.default} + clearButtonMode="never" + /> + </WithLabel> + <WithLabel label="while editing"> + <TextInput + style={styles.default} + clearButtonMode="while-editing" + /> + </WithLabel> + <WithLabel label="unless editing"> + <TextInput + style={styles.default} + clearButtonMode="unless-editing" + /> + </WithLabel> + <WithLabel label="always"> + <TextInput + style={styles.default} + clearButtonMode="always" + /> + </WithLabel> + </View> + ); + } + }, + { + title: 'Clear and select', + render: function() { + return ( + <View> + <WithLabel label="clearTextOnFocus"> + <TextInput + placeholder="text is cleared on focus" + defaultValue="text is cleared on focus" + style={styles.default} + clearTextOnFocus={true} + /> + </WithLabel> + <WithLabel label="selectTextOnFocus"> + <TextInput + placeholder="text is selected on focus" + defaultValue="text is selected on focus" + style={styles.default} + selectTextOnFocus={true} + /> + </WithLabel> + </View> + ); + } + }, + { + title: 'Blur on submit', + render: function(): React.Element<any> { return <BlurOnSubmitExample />; }, + }, + { + title: 'Multiline blur on submit', + render: function() { + return ( + <View> + <TextInput + style={styles.multiline} + placeholder="blurOnSubmit = true" + returnKeyType="next" + blurOnSubmit={true} + multiline={true} + onSubmitEditing={event => alert(event.nativeEvent.text)} + /> + </View> + ); + } + }, + { + title: 'Multiline', + render: function() { + return ( + <View> + <TextInput + placeholder="multiline text input" + multiline={true} + style={styles.multiline} + /> + <TextInput + placeholder="multiline text input with font styles and placeholder" + multiline={true} + clearTextOnFocus={true} + autoCorrect={true} + autoCapitalize="words" + placeholderTextColor="red" + keyboardType="url" + style={[styles.multiline, styles.multilineWithFontStyles]} + /> + <TextInput + placeholder="multiline text input with max length" + maxLength={5} + multiline={true} + style={styles.multiline} + /> + <TextInput + placeholder="uneditable multiline text input" + editable={false} + multiline={true} + style={styles.multiline} + /> + <TextInput + defaultValue="uneditable multiline text input with phone number detection: 88888888." + editable={false} + multiline={true} + style={styles.multiline} + dataDetectorTypes="phoneNumber" + /> + <TextInput + placeholder="multiline with children" + multiline={true} + enablesReturnKeyAutomatically={true} + returnKeyType="go" + style={styles.multiline}> + <View style={styles.multilineChild}/> + </TextInput> + </View> + ); + } + }, + { + title: 'Auto-expanding', + render: function() { + return ( + <View> + <AutoExpandingTextInput + placeholder="height increases with content" + enablesReturnKeyAutomatically={true} + returnKeyType="default" + /> + </View> + ); + } + }, + { + title: 'Attributed text', + render: function() { + return <TokenizedTextExample />; + } + }, + { + title: 'Text selection & cursor placement', + render: function() { + return ( + <View> + <SelectionExample + style={styles.default} + value="text selection can be changed" + /> + <SelectionExample + multiline + style={styles.multiline} + value={"multiline text selection\ncan also be changed"} + /> + </View> + ); + } + }, + { + title: 'TextInput maxLength', + render: function() { + return ( + <View> + <WithLabel label="maxLength: 5"> + <TextInput + maxLength={5} + style={styles.default} + /> + </WithLabel> + <WithLabel label="maxLength: 5 with placeholder"> + <TextInput + maxLength={5} + placeholder="ZIP code entry" + style={styles.default} + /> + </WithLabel> + <WithLabel label="maxLength: 5 with default value already set"> + <TextInput + maxLength={5} + defaultValue="94025" + style={styles.default} + /> + </WithLabel> + <WithLabel label="maxLength: 5 with very long default value already set"> + <TextInput + maxLength={5} + defaultValue="9402512345" + style={styles.default} + /> + </WithLabel> + </View> + ); + } + }, +];

ANDROID #

Edit on GitHub
'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + Text, + TextInput, + View, + StyleSheet, +} = ReactNative; + +class TextEventsExample extends React.Component { + state = { + curText: '<No Event>', + prevText: '<No Event>', + prev2Text: '<No Event>', + }; + + updateText = (text) => { + this.setState((state) => { + return { + curText: text, + prevText: state.curText, + prev2Text: state.prevText, + }; + }); + }; + + render() { + return ( + <View> + <TextInput + autoCapitalize="none" + placeholder="Enter text to see events" + autoCorrect={false} + onFocus={() => this.updateText('onFocus')} + onBlur={() => this.updateText('onBlur')} + onChange={(event) => this.updateText( + 'onChange text: ' + event.nativeEvent.text + )} + onEndEditing={(event) => this.updateText( + 'onEndEditing text: ' + event.nativeEvent.text + )} + onSubmitEditing={(event) => this.updateText( + 'onSubmitEditing text: ' + event.nativeEvent.text + )} + style={styles.singleLine} + /> + <Text style={styles.eventLabel}> + {this.state.curText}{'\n'} + (prev: {this.state.prevText}){'\n'} + (prev2: {this.state.prev2Text}) + </Text> + </View> + ); + } +} + +class AutoExpandingTextInput extends React.Component { + constructor(props) { + super(props); + this.state = { + text: 'React Native enables you to build world-class application experiences on native platforms using a consistent developer experience based on JavaScript and React. The focus of React Native is on developer efficiency across all the platforms you care about — learn once, write anywhere. Facebook uses React Native in multiple production apps and will continue investing in React Native.', + height: 0, + }; + } + render() { + return ( + <TextInput + {...this.props} + multiline={true} + onContentSizeChange={(event) => { + this.setState({height: event.nativeEvent.contentSize.height}); + }} + onChangeText={(text) => { + this.setState({text}); + }} + style={[styles.default, {height: Math.max(35, this.state.height)}]} + value={this.state.text} + /> + ); + } +} + +class RewriteExample extends React.Component { + constructor(props) { + super(props); + this.state = {text: ''}; + } + render() { + var limit = 20; + var remainder = limit - this.state.text.length; + var remainderColor = remainder > 5 ? 'blue' : 'red'; + return ( + <View style={styles.rewriteContainer}> + <TextInput + multiline={false} + maxLength={limit} + onChangeText={(text) => { + text = text.replace(/ /g, '_'); + this.setState({text}); + }} + style={styles.default} + value={this.state.text} + /> + <Text style={[styles.remainder, {color: remainderColor}]}> + {remainder} + </Text> + </View> + ); + } +} + +class TokenizedTextExample extends React.Component { + constructor(props) { + super(props); + this.state = {text: 'Hello #World'}; + } + render() { + + //define delimiter + let delimiter = /\s+/; + + //split string + let _text = this.state.text; + let token, index, parts = []; + while (_text) { + delimiter.lastIndex = 0; + token = delimiter.exec(_text); + if (token === null) { + break; + } + index = token.index; + if (token[0].length === 0) { + index = 1; + } + parts.push(_text.substr(0, index)); + parts.push(token[0]); + index = index + token[0].length; + _text = _text.slice(index); + } + parts.push(_text); + + //highlight hashtags + parts = parts.map((text) => { + if (/^#/.test(text)) { + return <Text key={text} style={styles.hashtag}>{text}</Text>; + } else { + return text; + } + }); + + return ( + <View> + <TextInput + multiline={true} + style={styles.multiline} + onChangeText={(text) => { + this.setState({text}); + }}> + <Text>{parts}</Text> + </TextInput> + </View> + ); + } +} + +class BlurOnSubmitExample extends React.Component { + focusNextField = (nextField) => { + this.refs[nextField].focus(); + }; + + render() { + return ( + <View> + <TextInput + ref="1" + style={styles.singleLine} + placeholder="blurOnSubmit = false" + returnKeyType="next" + blurOnSubmit={false} + onSubmitEditing={() => this.focusNextField('2')} + /> + <TextInput + ref="2" + style={styles.singleLine} + keyboardType="email-address" + placeholder="blurOnSubmit = false" + returnKeyType="next" + blurOnSubmit={false} + onSubmitEditing={() => this.focusNextField('3')} + /> + <TextInput + ref="3" + style={styles.singleLine} + keyboardType="url" + placeholder="blurOnSubmit = false" + returnKeyType="next" + blurOnSubmit={false} + onSubmitEditing={() => this.focusNextField('4')} + /> + <TextInput + ref="4" + style={styles.singleLine} + keyboardType="numeric" + placeholder="blurOnSubmit = false" + blurOnSubmit={false} + onSubmitEditing={() => this.focusNextField('5')} + /> + <TextInput + ref="5" + style={styles.singleLine} + keyboardType="numbers-and-punctuation" + placeholder="blurOnSubmit = true" + returnKeyType="done" + /> + </View> + ); + } +} + +class ToggleDefaultPaddingExample extends React.Component { + constructor(props) { + super(props); + this.state = {hasPadding: false}; + } + render() { + return ( + <View> + <TextInput style={this.state.hasPadding ? { padding: 0 } : null}/> + <Text onPress={() => this.setState({hasPadding: !this.state.hasPadding})}> + Toggle padding + </Text> + </View> + ); + } +} + +type SelectionExampleState = { + selection: { + start: number; + end: number; + }; + value: string; +}; + +class SelectionExample extends React.Component { + state: SelectionExampleState; + + _textInput: any; + + constructor(props) { + super(props); + this.state = { + selection: {start: 0, end: 0}, + value: props.value + }; + } + + onSelectionChange({nativeEvent: {selection}}) { + this.setState({selection}); + } + + getRandomPosition() { + var length = this.state.value.length; + return Math.round(Math.random() * length); + } + + select(start, end) { + this._textInput.focus(); + this.setState({selection: {start, end}}); + } + + selectRandom() { + var positions = [this.getRandomPosition(), this.getRandomPosition()].sort(); + this.select(...positions); + } + + placeAt(position) { + this.select(position, position); + } + + placeAtRandom() { + this.placeAt(this.getRandomPosition()); + } + + render() { + var length = this.state.value.length; + + return ( + <View> + <TextInput + multiline={this.props.multiline} + onChangeText={(value) => this.setState({value})} + onSelectionChange={this.onSelectionChange.bind(this)} + ref={textInput => (this._textInput = textInput)} + selection={this.state.selection} + style={this.props.style} + value={this.state.value} + /> + <View> + <Text> + selection = {JSON.stringify(this.state.selection)} + </Text> + <Text onPress={this.placeAt.bind(this, 0)}> + Place at Start (0, 0) + </Text> + <Text onPress={this.placeAt.bind(this, length)}> + Place at End ({length}, {length}) + </Text> + <Text onPress={this.placeAtRandom.bind(this)}> + Place at Random + </Text> + <Text onPress={this.select.bind(this, 0, length)}> + Select All + </Text> + <Text onPress={this.selectRandom.bind(this)}> + Select Random + </Text> + </View> + </View> + ); + } +} + +var styles = StyleSheet.create({ + multiline: { + height: 60, + fontSize: 16, + padding: 4, + marginBottom: 10, + }, + eventLabel: { + margin: 3, + fontSize: 12, + }, + singleLine: { + fontSize: 16, + padding: 4, + }, + singleLineWithHeightTextInput: { + height: 30, + }, + hashtag: { + color: 'blue', + fontWeight: 'bold', + }, +}); + +exports.title = '<TextInput>'; +exports.description = 'Single and multi-line text inputs.'; +exports.examples = [ + { + title: 'Auto-focus', + render: function() { + return ( + <TextInput + autoFocus={true} + style={styles.singleLine} + accessibilityLabel="I am the accessibility label for text input" + /> + ); + } + }, + { + title: "Live Re-Write (<sp> -> '_')", + render: function() { + return <RewriteExample />; + } + }, + { + title: 'Auto-capitalize', + render: function() { + var autoCapitalizeTypes = [ + 'none', + 'sentences', + 'words', + 'characters', + ]; + var examples = autoCapitalizeTypes.map((type) => { + return ( + <TextInput + key={type} + autoCapitalize={type} + placeholder={'autoCapitalize: ' + type} + style={styles.singleLine} + /> + ); + }); + return <View>{examples}</View>; + } + }, + { + title: 'Auto-correct', + render: function() { + return ( + <View> + <TextInput + autoCorrect={true} + placeholder="This has autoCorrect" + style={styles.singleLine} + /> + <TextInput + autoCorrect={false} + placeholder="This does not have autoCorrect" + style={styles.singleLine} + /> + </View> + ); + } + }, + { + title: 'Keyboard types', + render: function() { + var keyboardTypes = [ + 'default', + 'email-address', + 'numeric', + 'phone-pad', + ]; + var examples = keyboardTypes.map((type) => { + return ( + <TextInput + key={type} + keyboardType={type} + placeholder={'keyboardType: ' + type} + style={styles.singleLine} + /> + ); + }); + return <View>{examples}</View>; + } + }, + { + title: 'Blur on submit', + render: function(): React.Element { return <BlurOnSubmitExample />; }, + }, + { + title: 'Event handling', + render: function(): React.Element { return <TextEventsExample />; }, + }, + { + title: 'Colors and text inputs', + render: function() { + return ( + <View> + <TextInput + style={[styles.singleLine]} + defaultValue="Default color text" + /> + <TextInput + style={[styles.singleLine, {color: 'green'}]} + defaultValue="Green Text" + /> + <TextInput + placeholder="Default placeholder text color" + style={styles.singleLine} + /> + <TextInput + placeholder="Red placeholder text color" + placeholderTextColor="red" + style={styles.singleLine} + /> + <TextInput + placeholder="Default underline color" + style={styles.singleLine} + /> + <TextInput + placeholder="Blue underline color" + style={styles.singleLine} + underlineColorAndroid="blue" + /> + <TextInput + defaultValue="Same BackgroundColor as View " + style={[styles.singleLine, {backgroundColor: 'rgba(100, 100, 100, 0.3)'}]}> + <Text style={{backgroundColor: 'rgba(100, 100, 100, 0.3)'}}> + Darker backgroundColor + </Text> + </TextInput> + <TextInput + defaultValue="Highlight Color is red" + selectionColor={'red'} + style={styles.singleLine}> + </TextInput> + </View> + ); + } + }, + { + title: 'Text input, themes and heights', + render: function() { + return ( + <TextInput + placeholder="If you set height, beware of padding set from themes" + style={[styles.singleLineWithHeightTextInput]} + /> + ); + } + }, + { + title: 'fontFamily, fontWeight and fontStyle', + render: function() { + return ( + <View> + <TextInput + style={[styles.singleLine, {fontFamily: 'sans-serif'}]} + placeholder="Custom fonts like Sans-Serif are supported" + /> + <TextInput + style={[styles.singleLine, {fontFamily: 'sans-serif', fontWeight: 'bold'}]} + placeholder="Sans-Serif bold" + /> + <TextInput + style={[styles.singleLine, {fontFamily: 'sans-serif', fontStyle: 'italic'}]} + placeholder="Sans-Serif italic" + /> + <TextInput + style={[styles.singleLine, {fontFamily: 'serif'}]} + placeholder="Serif" + /> + </View> + ); + } + }, + { + title: 'Passwords', + render: function() { + return ( + <View> + <TextInput + defaultValue="iloveturtles" + secureTextEntry={true} + style={styles.singleLine} + /> + <TextInput + secureTextEntry={true} + style={[styles.singleLine, {color: 'red'}]} + placeholder="color is supported too" + placeholderTextColor="red" + /> + </View> + ); + } + }, + { + title: 'Editable', + render: function() { + return ( + <TextInput + defaultValue="Can't touch this! (>'-')> ^(' - ')^ <('-'<) (>'-')> ^(' - ')^" + editable={false} + style={styles.singleLine} + /> + ); + } + }, + { + title: 'Multiline', + render: function() { + return ( + <View> + <TextInput + autoCorrect={true} + placeholder="multiline, aligned top-left" + placeholderTextColor="red" + multiline={true} + style={[styles.multiline, {textAlign: 'left', textAlignVertical: 'top'}]} + /> + <TextInput + autoCorrect={true} + placeholder="multiline, aligned center" + placeholderTextColor="green" + multiline={true} + style={[styles.multiline, {textAlign: 'center', textAlignVertical: 'center'}]} + /> + <TextInput + autoCorrect={true} + multiline={true} + style={[styles.multiline, {color: 'blue'}, {textAlign: 'right', textAlignVertical: 'bottom'}]}> + <Text style={styles.multiline}>multiline with children, aligned bottom-right</Text> + </TextInput> + </View> + ); + } + }, + { + title: 'Fixed number of lines', + platform: 'android', + render: function() { + return ( + <View> + <TextInput numberOfLines={2} + multiline={true} + placeholder="Two line input" + /> + <TextInput numberOfLines={5} + multiline={true} + placeholder="Five line input" + /> + </View> + ); + } + }, + { + title: 'Auto-expanding', + render: function() { + return ( + <View> + <AutoExpandingTextInput + placeholder="height increases with content" + enablesReturnKeyAutomatically={true} + returnKeyType="done" + /> + </View> + ); + } + }, + { + title: 'Attributed text', + render: function() { + return <TokenizedTextExample />; + } + }, + { + title: 'Return key', + render: function() { + var returnKeyTypes = [ + 'none', + 'go', + 'search', + 'send', + 'done', + 'previous', + 'next', + ]; + var returnKeyLabels = [ + 'Compile', + 'React Native', + ]; + var examples = returnKeyTypes.map((type) => { + return ( + <TextInput + key={type} + returnKeyType={type} + placeholder={'returnKeyType: ' + type} + style={styles.singleLine} + /> + ); + }); + var types = returnKeyLabels.map((type) => { + return ( + <TextInput + key={type} + returnKeyLabel={type} + placeholder={'returnKeyLabel: ' + type} + style={styles.singleLine} + /> + ); + }); + return <View>{examples}{types}</View>; + } + }, + { + title: 'Inline Images', + render: function() { + return ( + <View> + <TextInput + inlineImageLeft="ic_menu_black_24dp" + placeholder="This has drawableLeft set" + style={styles.singleLine} + /> + <TextInput + inlineImageLeft="ic_menu_black_24dp" + inlineImagePadding={30} + placeholder="This has drawableLeft and drawablePadding set" + style={styles.singleLine} + /> + <TextInput + placeholder="This does not have drawable props set" + style={styles.singleLine} + /> + </View> + ); + } + }, + { + title: 'Toggle Default Padding', + render: function(): React.Element { return <ToggleDefaultPaddingExample />; }, + }, + { + title: 'Text selection & cursor placement', + render: function() { + return ( + <View> + <SelectionExample + style={styles.default} + value="text selection can be changed" + /> + <SelectionExample + multiline + style={styles.multiline} + value={"multiline text selection\ncan also be changed"} + /> + </View> + ); + } + }, +];
\ No newline at end of file diff --git a/releases/0.37/docs/timepickerandroid.html b/releases/0.37/docs/timepickerandroid.html new file mode 100644 index 00000000000..0c6d23bda34 --- /dev/null +++ b/releases/0.37/docs/timepickerandroid.html @@ -0,0 +1,128 @@ +TimePickerAndroid

TimePickerAndroid #

Opens the standard Android time picker dialog.

Example #

try { + const {action, hour, minute} = await TimePickerAndroid.open({ + hour: 14, + minute: 0, + is24Hour: false, // Will display '2 PM' + }); + if (action !== TimePickerAndroid.dismissedAction) { + // Selected hour (0-23), minute (0-59) + } +} catch ({code, message}) { + console.warn('Cannot open time picker', message); +}

Methods #

static open(options) #

Opens the standard Android time picker dialog.

The available keys for the options object are: + hour (0-23) - the hour to show, defaults to the current time + minute (0-59) - the minute to show, defaults to the current time + * is24Hour (boolean) - If true, the picker uses the 24-hour format. If false, + the picker shows an AM/PM chooser. If undefined, the default for the current locale + is used.

Returns a Promise which will be invoked an object containing action, hour (0-23), +minute (0-59) if the user picked a time. If the user dismissed the dialog, the Promise will +still be resolved with action being TimePickerAndroid.dismissedAction and all the other keys +being undefined. Always check whether the action before reading the values.

static timeSetAction(0) #

A time has been selected.

static dismissedAction(0) #

The dialog has been dismissed.

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + TimePickerAndroid, + StyleSheet, + Text, + TouchableWithoutFeedback, +} = ReactNative; + +var UIExplorerBlock = require('./UIExplorerBlock'); +var UIExplorerPage = require('./UIExplorerPage'); + +class TimePickerAndroidExample extends React.Component { + static title = 'TimePickerAndroid'; + static description = 'Standard Android time picker dialog'; + + state = { + isoFormatText: 'pick a time (24-hour format)', + presetHour: 4, + presetMinute: 4, + presetText: 'pick a time, default: 4:04AM', + simpleText: 'pick a time', + }; + + showPicker = async (stateKey, options) => { + try { + const {action, minute, hour} = await TimePickerAndroid.open(options); + var newState = {}; + if (action === TimePickerAndroid.timeSetAction) { + newState[stateKey + 'Text'] = _formatTime(hour, minute); + newState[stateKey + 'Hour'] = hour; + newState[stateKey + 'Minute'] = minute; + } else if (action === TimePickerAndroid.dismissedAction) { + newState[stateKey + 'Text'] = 'dismissed'; + } + this.setState(newState); + } catch ({code, message}) { + console.warn(`Error in example '${stateKey}': `, message); + } + }; + + render() { + return ( + <UIExplorerPage title="TimePickerAndroid"> + <UIExplorerBlock title="Simple time picker"> + <TouchableWithoutFeedback + onPress={this.showPicker.bind(this, 'simple', {})}> + <Text style={styles.text}>{this.state.simpleText}</Text> + </TouchableWithoutFeedback> + </UIExplorerBlock> + <UIExplorerBlock title="Time picker with pre-set time"> + <TouchableWithoutFeedback + onPress={this.showPicker.bind(this, 'preset', { + hour: this.state.presetHour, + minute: this.state.presetMinute, + })}> + <Text style={styles.text}>{this.state.presetText}</Text> + </TouchableWithoutFeedback> + </UIExplorerBlock> + + <UIExplorerBlock title="Time picker with 24-hour time format"> + <TouchableWithoutFeedback + onPress={this.showPicker.bind(this, 'isoFormat', { + hour: this.state.isoFormatHour, + minute: this.state.isoFormatMinute, + is24Hour: true, + })}> + <Text style={styles.text}>{this.state.isoFormatText}</Text> + </TouchableWithoutFeedback> + </UIExplorerBlock> + </UIExplorerPage> + ); + } +} + +/** + * Returns e.g. '3:05'. + */ +function _formatTime(hour, minute) { + return hour + ':' + (minute < 10 ? '0' + minute : minute); +} + +var styles = StyleSheet.create({ + text: { + color: 'black', + }, +}); + +module.exports = TimePickerAndroidExample;
\ No newline at end of file diff --git a/releases/0.37/docs/timers.html b/releases/0.37/docs/timers.html new file mode 100644 index 00000000000..a8f94a47af3 --- /dev/null +++ b/releases/0.37/docs/timers.html @@ -0,0 +1,35 @@ +Timers

Timers #

Timers are an important part of an application and React Native implements the browser timers.

Timers #

  • setTimeout, clearTimeout
  • setInterval, clearInterval
  • setImmediate, clearImmediate
  • requestAnimationFrame, cancelAnimationFrame

requestAnimationFrame(fn) is not the same as setTimeout(fn, 0) - the former will fire after all the frame has flushed, whereas the latter will fire as quickly as possible (over 1000x per second on a iPhone 5S).

setImmediate is executed at the end of the current JavaScript execution block, right before sending the batched response back to native. Note that if you call setImmediate within a setImmediate callback, it will be executed right away, it won't yield back to native in between.

The Promise implementation uses setImmediate as its asynchronicity primitive.

InteractionManager #

One reason why well-built native apps feel so smooth is by avoiding expensive operations during interactions and animations. In React Native, we currently have a limitation that there is only a single JS execution thread, but you can use InteractionManager to make sure long-running work is scheduled to start after any interactions/animations have completed.

Applications can schedule tasks to run after interactions with the following:

InteractionManager.runAfterInteractions(() => { + // ...long-running synchronous task... +});

Compare this to other scheduling alternatives:

  • requestAnimationFrame(): for code that animates a view over time.
  • setImmediate/setTimeout/setInterval(): run code later, note this may delay animations.
  • runAfterInteractions(): run code later, without delaying active animations.

The touch handling system considers one or more active touches to be an 'interaction' and will delay runAfterInteractions() callbacks until all touches have ended or been cancelled.

InteractionManager also allows applications to register animations by creating an interaction 'handle' on animation start, and clearing it upon completion:

var handle = InteractionManager.createInteractionHandle(); +// run animation... (`runAfterInteractions` tasks are queued) +// later, on animation completion: +InteractionManager.clearInteractionHandle(handle); +// queued tasks run if all handles were cleared

TimerMixin #

We found out that the primary cause of fatals in apps created with React Native was due to timers firing after a component was unmounted. To solve this recurring issue, we introduced TimerMixin. If you include TimerMixin, then you can replace your calls to setTimeout(fn, 500) with this.setTimeout(fn, 500) (just prepend this.) and everything will be properly cleaned up for you when the component unmounts.

This library does not ship with React Native - in order to use it on your project, you will need to install it with npm i react-timer-mixin --save from your project directory.

import TimerMixin from 'react-timer-mixin'; + +var Component = React.createClass({ + mixins: [TimerMixin], + componentDidMount: function() { + this.setTimeout( + () => { console.log('I do not leak!'); }, + 500 + ); + } +});

This will eliminate a lot of hard work tracking down bugs, such as crashes caused by timeouts firing after a component has been unmounted.

Keep in mind that if you use ES6 classes for your React components there is no built-in API for mixins. To use TimerMixin with ES6 classes, we recommend react-mixin.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/toastandroid.html b/releases/0.37/docs/toastandroid.html new file mode 100644 index 00000000000..7e237ec62f3 --- /dev/null +++ b/releases/0.37/docs/toastandroid.html @@ -0,0 +1,105 @@ +ToastAndroid

ToastAndroid #

This exposes the native ToastAndroid module as a JS module. This has a function 'show' +which takes the following parameters:

  1. String message: A string with the text to toast
  2. int duration: The duration of the toast. May be ToastAndroid.SHORT or ToastAndroid.LONG

There is also a function showWithGravity to specify the layout gravity. May be +ToastAndroid.TOP, ToastAndroid.BOTTOM, ToastAndroid.CENTER.

Basic usage:

ToastAndroid.show('A pikachu appeared nearby !', ToastAndroid.SHORT); +ToastAndroid.showWithGravity('All Your Base Are Belong To Us', ToastAndroid.SHORT, ToastAndroid.CENTER);

Methods #

static show(message, duration) #

static showWithGravity(message, duration, gravity) #

Properties #

SHORT: MemberExpression #

// Toast duration constants

LONG: MemberExpression #

TOP: MemberExpression #

// Toast gravity constants

BOTTOM: MemberExpression #

CENTER: MemberExpression #

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + StyleSheet, + Text, + ToastAndroid, + TouchableWithoutFeedback, +} = ReactNative; + +var UIExplorerBlock = require('UIExplorerBlock'); +var UIExplorerPage = require('UIExplorerPage'); + +class ToastExample extends React.Component { + static title = 'Toast Example'; + static description = 'Example that demonstrates the use of an Android Toast to provide feedback.'; + state = {}; + + render() { + return ( + <UIExplorerPage title="ToastAndroid"> + <UIExplorerBlock title="Simple toast"> + <TouchableWithoutFeedback + onPress={() => + ToastAndroid.show('This is a toast with short duration', ToastAndroid.SHORT)}> + <Text style={styles.text}>Click me.</Text> + </TouchableWithoutFeedback> + </UIExplorerBlock> + <UIExplorerBlock title="Toast with long duration"> + <TouchableWithoutFeedback + onPress={() => + ToastAndroid.show('This is a toast with long duration', ToastAndroid.LONG)}> + <Text style={styles.text}>Click me.</Text> + </TouchableWithoutFeedback> + </UIExplorerBlock> + <UIExplorerBlock title="Toast with top gravity"> + <TouchableWithoutFeedback + onPress={() => + ToastAndroid.showWithGravity( + 'This is a toast with top gravity', + ToastAndroid.SHORT, + ToastAndroid.TOP, + ) + }> + <Text style={styles.text}>Click me.</Text> + </TouchableWithoutFeedback> + </UIExplorerBlock> + <UIExplorerBlock title="Toast with center gravity"> + <TouchableWithoutFeedback + onPress={() => + ToastAndroid.showWithGravity( + 'This is a toast with center gravity', + ToastAndroid.SHORT, + ToastAndroid.CENTER, + ) + }> + <Text style={styles.text}>Click me.</Text> + </TouchableWithoutFeedback> + </UIExplorerBlock> + <UIExplorerBlock title="Toast with bottom gravity"> + <TouchableWithoutFeedback + onPress={() => + ToastAndroid.showWithGravity( + 'This is a toast with bottom gravity', + ToastAndroid.SHORT, + ToastAndroid.BOTTOM, + ) + }> + <Text style={styles.text}>Click me.</Text> + </TouchableWithoutFeedback> + </UIExplorerBlock> + </UIExplorerPage> + ); + } +} + +var styles = StyleSheet.create({ + text: { + color: 'black', + }, +}); + +module.exports = ToastExample;
\ No newline at end of file diff --git a/releases/0.37/docs/toolbarandroid.html b/releases/0.37/docs/toolbarandroid.html new file mode 100644 index 00000000000..2cde15d9d90 --- /dev/null +++ b/releases/0.37/docs/toolbarandroid.html @@ -0,0 +1,169 @@ +ToolbarAndroid

ToolbarAndroid #

React component that wraps the Android-only Toolbar widget. A Toolbar can display a logo, +navigation icon (e.g. hamburger menu), a title & subtitle and a list of actions. The title and +subtitle are expanded so the logo and navigation icons are displayed on the left, title and +subtitle in the middle and the actions on the right.

If the toolbar has an only child, it will be displayed between the title and actions.

Although the Toolbar supports remote images for the logo, navigation and action icons, this +should only be used in DEV mode where require('./some_icon.png') translates into a packager +URL. In release mode you should always use a drawable resource for these icons. Using +require('./some_icon.png') will do this automatically for you, so as long as you don't +explicitly use e.g. {uri: 'http://...'}, you will be good.

Example:

render: function() { + return ( + <ToolbarAndroid + logo={require('./app_logo.png')} + title="AwesomeApp" + actions={[{title: 'Settings', icon: require('./icon_settings.png'), show: 'always'}]} + onActionSelected={this.onActionSelected} /> + ) +}, +onActionSelected: function(position) { + if (position === 0) { // index of 'Settings' + showSettings(); + } +}

Props #

actions [{title: string, icon: optionalImageSource, show: enum('always', 'ifRoom', 'never'), showWithText: bool}] #

Sets possible actions on the toolbar as part of the action menu. These are displayed as icons +or text on the right side of the widget. If they don't fit they are placed in an 'overflow' +menu.

This property takes an array of objects, where each object has the following keys:

  • title: required, the title of this action
  • icon: the icon for this action, e.g. require('./some_icon.png')
  • show: when to show this action as an icon or hide it in the overflow menu: always, +ifRoom or never
  • showWithText: boolean, whether to show text alongside the icon or not

contentInsetEnd number #

Sets the content inset for the toolbar ending edge.

The content inset affects the valid area for Toolbar content other than +the navigation button and menu. Insets define the minimum margin for +these components and can be used to effectively align Toolbar content +along well-known gridlines.

contentInsetStart number #

Sets the content inset for the toolbar starting edge.

The content inset affects the valid area for Toolbar content other than +the navigation button and menu. Insets define the minimum margin for +these components and can be used to effectively align Toolbar content +along well-known gridlines.

logo optionalImageSource #

Sets the toolbar logo.

navIcon optionalImageSource #

Sets the navigation icon.

onActionSelected function #

Callback that is called when an action is selected. The only argument that is passed to the +callback is the position of the action in the actions array.

onIconClicked function #

Callback called when the icon is selected.

overflowIcon optionalImageSource #

Sets the overflow icon.

rtl bool #

Used to set the toolbar direction to RTL. +In addition to this property you need to add

android:supportsRtl="true"

to your application AndroidManifest.xml and then call +setLayoutDirection(LayoutDirection.RTL) in your MainActivity +onCreate method.

subtitle string #

Sets the toolbar subtitle.

subtitleColor color #

Sets the toolbar subtitle color.

testID string #

Used to locate this view in end-to-end tests.

title string #

Sets the toolbar title.

titleColor color #

Sets the toolbar title color.

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + StyleSheet, + Text, + View, +} = ReactNative; +var UIExplorerBlock = require('./UIExplorerBlock'); +var UIExplorerPage = require('./UIExplorerPage'); + +var Switch = require('Switch'); +var ToolbarAndroid = require('ToolbarAndroid'); + +class ToolbarAndroidExample extends React.Component { + static title = '<ToolbarAndroid>'; + static description = 'Examples of using the Android toolbar.'; + + state = { + actionText: 'Example app with toolbar component', + toolbarSwitch: false, + colorProps: { + titleColor: '#3b5998', + subtitleColor: '#6a7180', + }, + }; + + render() { + return ( + <UIExplorerPage title="<ToolbarAndroid>"> + <UIExplorerBlock title="Toolbar with title/subtitle and actions"> + <ToolbarAndroid + actions={toolbarActions} + navIcon={require('image!ic_menu_black_24dp')} + onActionSelected={this._onActionSelected} + onIconClicked={() => this.setState({actionText: 'Icon clicked'})} + style={styles.toolbar} + subtitle={this.state.actionText} + title="Toolbar" /> + <Text>{this.state.actionText}</Text> + </UIExplorerBlock> + <UIExplorerBlock title="Toolbar with logo & custom title view (a View with Switch+Text)"> + <ToolbarAndroid + logo={require('image!launcher_icon')} + style={styles.toolbar}> + <View style={{height: 56, flexDirection: 'row', alignItems: 'center'}}> + <Switch + value={this.state.toolbarSwitch} + onValueChange={(value) => this.setState({'toolbarSwitch': value})} /> + <Text>{'\'Tis but a switch'}</Text> + </View> + </ToolbarAndroid> + </UIExplorerBlock> + <UIExplorerBlock title="Toolbar with no icon"> + <ToolbarAndroid + actions={toolbarActions} + style={styles.toolbar} + subtitle="There be no icon here" /> + </UIExplorerBlock> + <UIExplorerBlock title="Toolbar with navIcon & logo, no title"> + <ToolbarAndroid + actions={toolbarActions} + logo={require('image!launcher_icon')} + navIcon={require('image!ic_menu_black_24dp')} + style={styles.toolbar} /> + </UIExplorerBlock> + <UIExplorerBlock title="Toolbar with custom title colors"> + <ToolbarAndroid + navIcon={require('image!ic_menu_black_24dp')} + onIconClicked={() => this.setState({colorProps: {}})} + title="Wow, such toolbar" + style={styles.toolbar} + subtitle="Much native" + {...this.state.colorProps} /> + <Text> + Touch the icon to reset the custom colors to the default (theme-provided) ones. + </Text> + </UIExplorerBlock> + <UIExplorerBlock title="Toolbar with remote logo & navIcon"> + <ToolbarAndroid + actions={[{title: 'Bunny', icon: require('./bunny.png'), show: 'always'}]} + logo={require('./hawk.png')} + navIcon={require('./bunny.png')} + title="Bunny and Hawk" + style={styles.toolbar} /> + </UIExplorerBlock> + <UIExplorerBlock title="Toolbar with custom overflowIcon"> + <ToolbarAndroid + actions={toolbarActions} + overflowIcon={require('./bunny.png')} + style={styles.toolbar} /> + </UIExplorerBlock> + </UIExplorerPage> + ); + } + + _onActionSelected = (position) => { + this.setState({ + actionText: 'Selected ' + toolbarActions[position].title, + }); + }; +} + +var toolbarActions = [ + {title: 'Create', icon: require('image!ic_create_black_48dp'), show: 'always'}, + {title: 'Filter'}, + {title: 'Settings', icon: require('image!ic_settings_black_48dp'), show: 'always'}, +]; + +var styles = StyleSheet.create({ + toolbar: { + backgroundColor: '#e9eaed', + height: 56, + }, +}); + +module.exports = ToolbarAndroidExample;
\ No newline at end of file diff --git a/releases/0.37/docs/touchablehighlight.html b/releases/0.37/docs/touchablehighlight.html new file mode 100644 index 00000000000..3799025a107 --- /dev/null +++ b/releases/0.37/docs/touchablehighlight.html @@ -0,0 +1,35 @@ +TouchableHighlight

TouchableHighlight #

A wrapper for making views respond properly to touches. +On press down, the opacity of the wrapped view is decreased, which allows +the underlay color to show through, darkening or tinting the view. The +underlay comes from adding a view to the view hierarchy, which can sometimes +cause unwanted visual artifacts if not used correctly, for example if the +backgroundColor of the wrapped view isn't explicitly set to an opaque color.

Example:

renderButton: function() { + return ( + <TouchableHighlight onPress={this._onPressButton}> + <Image + style={styles.button} + source={require('./myButton.png')} + /> + </TouchableHighlight> + ); +},

NOTE: TouchableHighlight must have one child (not zero or more than one)

If you wish to have several child components, wrap them in a View.

Props #

activeOpacity number #

Determines what the opacity of the wrapped view should be when touch is +active.

onHideUnderlay function #

Called immediately after the underlay is hidden

onShowUnderlay function #

Called immediately after the underlay is shown

underlayColor color #

The color of the underlay that will show through when the touch is +active.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/touchablenativefeedback.html b/releases/0.37/docs/touchablenativefeedback.html new file mode 100644 index 00000000000..33924e122d2 --- /dev/null +++ b/releases/0.37/docs/touchablenativefeedback.html @@ -0,0 +1,48 @@ +TouchableNativeFeedback

TouchableNativeFeedback #

A wrapper for making views respond properly to touches (Android only). +On Android this component uses native state drawable to display touch +feedback. At the moment it only supports having a single View instance as a +child node, as it's implemented by replacing that View with another instance +of RCTView node with some additional properties set.

Background drawable of native feedback touchable can be customized with +background property.

Example:

renderButton: function() { + return ( + <TouchableNativeFeedback + onPress={this._onPressButton} + background={TouchableNativeFeedback.SelectableBackground()}> + <View style={{width: 150, height: 100, backgroundColor: 'red'}}> + <Text style={{margin: 30}}>Button</Text> + </View> + </TouchableNativeFeedback> + ); +},

Props #

background backgroundPropType #

Determines the type of background drawable that's going to be used to +display feedback. It takes an object with type property and extra data +depending on the type. It's recommended to use one of the static +methods to generate that dictionary.

useForeground bool #

Set to true to add the ripple effect to the foreground of the view, instead of the +background. This is useful if one of your child views has a background of its own, or you're +e.g. displaying images, and you don't want the ripple to be covered by them.

Check TouchableNativeFeedback.canUseNativeForeground() first, as this is only available on +Android 6.0 and above. If you try to use this on older versions you will get a warning and +fallback to background.

Methods #

static SelectableBackground(0) #

Creates an object that represents android theme's default background for +selectable elements (?android:attr/selectableItemBackground).

static SelectableBackgroundBorderless(0) #

Creates an object that represent android theme's default background for borderless +selectable elements (?android:attr/selectableItemBackgroundBorderless). +Available on android API level 21+.

static Ripple(color, borderless) #

Creates an object that represents ripple drawable with specified color (as a +string). If property borderless evaluates to true the ripple will +render outside of the view bounds (see native actionbar buttons as an +example of that behavior). This background type is available on Android +API level 21+.

Parameters:
Name and TypeDescription
color

string

The ripple color

borderless

boolean

If the ripple can render outside it's bounds

static canUseNativeForeground(0) #

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/touchableopacity.html b/releases/0.37/docs/touchableopacity.html new file mode 100644 index 00000000000..69ea6c5d07b --- /dev/null +++ b/releases/0.37/docs/touchableopacity.html @@ -0,0 +1,32 @@ +TouchableOpacity

TouchableOpacity #

A wrapper for making views respond properly to touches. +On press down, the opacity of the wrapped view is decreased, dimming it. +This is done without actually changing the view hierarchy, and in general is +easy to add to an app without weird side-effects.

Example:

renderButton: function() { + return ( + <TouchableOpacity onPress={this._onPressButton}> + <Image + style={styles.button} + source={require('./myButton.png')} + /> + </TouchableOpacity> + ); +},

Props #

activeOpacity number #

Determines what the opacity of the wrapped view should be when touch is +active. Defaults to 0.2.

Methods #

setOpacityTo(value) #

Animate the touchable to a new opacity.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/touchablewithoutfeedback.html b/releases/0.37/docs/touchablewithoutfeedback.html new file mode 100644 index 00000000000..5542267990c --- /dev/null +++ b/releases/0.37/docs/touchablewithoutfeedback.html @@ -0,0 +1,31 @@ +TouchableWithoutFeedback

TouchableWithoutFeedback #

Do not use unless you have a very good reason. All the elements that +respond to press should have a visual feedback when touched. This is +one of the primary reason a "web" app doesn't feel "native".

NOTE: TouchableWithoutFeedback supports only one child

If you wish to have several child components, wrap them in a View.

Props #

accessibilityComponentType View.AccessibilityComponentType #

accessibilityTraits View.AccessibilityTraits, [object Object] #

accessible bool #

delayLongPress number #

Delay in ms, from onPressIn, before onLongPress is called.

delayPressIn number #

Delay in ms, from the start of the touch, before onPressIn is called.

delayPressOut number #

Delay in ms, from the release of the touch, before onPressOut is called.

disabled bool #

If true, disable all interactions for this component.

hitSlop {top: number, left: number, bottom: number, right: number} #

This defines how far your touch can start away from the button. This is +added to pressRetentionOffset when moving off of the button. + NOTE +The touch area never extends past the parent view bounds and the Z-index +of sibling views always takes precedence if a touch hits two overlapping +views.

onLayout function #

Invoked on mount and layout changes with

{nativeEvent: {layout: {x, y, width, height}}}

onLongPress function #

onPress function #

Called when the touch is released, but not if cancelled (e.g. by a scroll +that steals the responder lock).

onPressIn function #

onPressOut function #

pressRetentionOffset {top: number, left: number, bottom: number, right: number} #

When the scroll view is disabled, this defines how far your touch may +move off of the button, before deactivating the button. Once deactivated, +try moving it back and you'll see that the button is once again +reactivated! Move it back and forth several times while the scroll view +is disabled. Ensure you pass in a constant to reduce memory allocations.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/transforms.html b/releases/0.37/docs/transforms.html new file mode 100644 index 00000000000..965dea2702b --- /dev/null +++ b/releases/0.37/docs/transforms.html @@ -0,0 +1,19 @@ +Transforms

Transforms #

Props #

decomposedMatrix DecomposedMatrixPropType #

transform [{perspective: number}, {rotate: string}, {rotateX: string}, {rotateY: string}, {rotateZ: string}, {scale: number}, {scaleX: number}, {scaleY: number}, {translateX: number}, {translateY: number}, {skewX: string}, {skewY: string}] #

transformMatrix TransformMatrixPropType #

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/troubleshooting.html b/releases/0.37/docs/troubleshooting.html new file mode 100644 index 00000000000..c6b773659fc --- /dev/null +++ b/releases/0.37/docs/troubleshooting.html @@ -0,0 +1,27 @@ +Troubleshooting

Troubleshooting #

These are some common issues you may run into while setting up React Native. If you encounter something that is not listed here, try searching for the issue in GitHub.

Port already in use #

The React Native packager runs on port 8081. If another process is already using that port (such as McAfee Antivirus on Windows), you can either terminate that process, or change the port that the packager uses.

Terminating a process on port 8081 #

Run the following command on a Mac to find the id for the process that is listening on port 8081:

$ sudo lsof -n -i4TCP:8081 | grep LISTEN

Then run the following to terminate the process:

$ kill -9 <PID>

On Windows you can find the process using port 8081 using Resource Monitor and stop it using Task Manager.

Using a port other than 8081 #

You can configure the packager to use a port other than 8081 by using the port parameter:

$ react-native start --port=8088

You will also need to update your applications to load the JavaScript bundle from the new port.

To change the port used by an iOS application, edit the AppDelegate.m file in the ios folder. Scroll down to the line where the bundle location is defined, and replace 8081 with the new port.

jsCodeLocation = [NSURL URLWithString:@"http://localhost:8088/index.ios.bundle"];

NPM locking error #

If you encounter an error such as "npm WARN locking Error: EACCES" while using the React Native CLI, try running the following:

sudo chown -R $USER ~/.npm +sudo chown -R $USER /usr/local/lib/node_modules

Missing libraries for React #

If you added React Native manually to your project, make sure you have included all the relevant dependencies that you are using, like RCTText.xcodeproj, RCTImage.xcodeproj. Next, the binaries built by these dependencies have to be linked to your app binary. Use the Linked Frameworks and Binaries section in the Xcode project settings. More detailed steps are here: Linking Libraries.

If you are using CocoaPods, verify that you have added React along with the subspecs to the Podfile. For example, if you were using the <Text />, <Image /> and fetch() APIs, you would need to add these in your Podfile:

pod 'React', :path => '../node_modules/react-native', :subspecs => [ + 'RCTText', + 'RCTImage', + 'RCTNetwork', + 'RCTWebSocket', +]

Next, make sure you have run pod install and that a Pods/ directory has been created in your project with React installed. CocoaPods will instruct you to use the generated .xcworkspace file henceforth to be able to use these installed dependencies.

Argument list too long: recursive header expansion failed #

In the project's build settings, User Search Header Paths and Header Search Paths are two configs that specify where Xcode should look for #import header files specified in the code. For Pods, CocoaPods uses a default array of specific folders to look in. Verify that this particular config is not overwritten, and that none of the folders configured are too large. If one of the folders is a large folder, Xcode will attempt to recursively search the entire directory and throw above error at some point.

To revert the User Search Header Paths and Header Search Paths build settings to their defaults set by CocoaPods - select the entry in the Build Settings panel, and hit delete. It will remove the custom override and return to the CocoaPod defaults.

No transports available #

React Native implements a polyfill for WebSockets. These polyfills are initialized as part of the react-native module that you include in your application through import React from 'react'. If you load another module that requires WebSockets, such as Firebase, be sure to load/require it after react-native:

import React from 'react'; +import Firebase from 'firebase';

Shell Command Unresponsive Exception #

If you encounter a ShellCommandUnresponsiveException exception such as:

Execution failed for task ':app:installDebug'. + com.android.builder.testing.api.DeviceException: com.android.ddmlib.ShellCommandUnresponsiveException

Try downgrading your Gradle version to 1.2.3 in android/build.gradle.

react-native init hangs #

If you run into issues where running react-native init hangs in your system, try running it again in verbose mode and refering to #2797 for common causes:

react-native init --verbose

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/tutorial.html b/releases/0.37/docs/tutorial.html new file mode 100644 index 00000000000..c1559c6dcc4 --- /dev/null +++ b/releases/0.37/docs/tutorial.html @@ -0,0 +1,32 @@ +Tutorial

Tutorial #

React Native is like React, but it uses native components instead of web components as building blocks. So to understand the basic structure of a React Native app, you need to understand some of the basic React concepts, like JSX, components, state, and props. If you already know React, you still need to learn some React-Native-specific stuff, like the native components. This +tutorial is aimed at all audiences, whether you have React experience or not.

Let's do this thing.

Hello World #

In accordance with the ancient traditions of our people, we must first build an app that does nothing except say "Hello world". Here it is:

import React, { Component } from 'react'; +import { AppRegistry, Text } from 'react-native'; + +class HelloWorldApp extends Component { + render() { + return ( + <Text>Hello world!</Text> + ); + } +} + +AppRegistry.registerComponent('HelloWorldApp', () => HelloWorldApp);

If you are feeling curious, you can play around with sample code directly in the web simulators. You can also paste it into your index.ios.js or index.android.js file to create a real app on your local machine.

What's going on here? #

Some of the things in here might not look like JavaScript to you. Don't panic. This is the future.

First of all, ES2015 (also known as ES6) is a set of improvements to JavaScript that is now part of the official standard, but not yet supported by all browsers, so often it isn't used yet in web development. React Native ships with ES2015 support, so you can use this stuff without worrying about compatibility. import, from, class, extends, and the () => syntax in the example above are all ES2015 features. If you aren't familiar with ES2015, you can probably pick it up just by reading through sample code like this tutorial has. If you want, this page has a good overview of ES2015 features.

The other unusual thing in this code example is <Text>Hello world!</Text>. This is JSX - a syntax for embedding XML within JavaScript. Many frameworks use a special templating language which lets you embed code inside markup language. In React, this is reversed. JSX lets you write your markup language inside code. It looks like HTML on the web, except instead of web things like <div> or <span>, you use React components. In this case, <Text> +is a built-in component that just displays some text.

Component and AppRegistry #

So this code is defining HelloWorldApp, a new Component, and it's registering it with the AppRegistry. When you're building a React Native app, you'll be making new components a lot. Anything you see on the screen is some sort of component. A component can be pretty simple - the only thing that's required is a render function which returns some JSX to render.

The AppRegistry just tells React Native which component is the root one for the whole application. You won't be thinking about AppRegistry a lot - there will probably just be one call to AppRegistry.registerComponent in your whole app. It's included in these examples so you can paste the whole thing into your index.ios.js or index.android.js file and get it running.

This App Doesn't Do Very Much #

Good point. To make components do more interesting things, you need to learn about Props.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/upgrading.html b/releases/0.37/docs/upgrading.html new file mode 100644 index 00000000000..93503777386 --- /dev/null +++ b/releases/0.37/docs/upgrading.html @@ -0,0 +1,27 @@ +Upgrading

Upgrading #

Upgrading to new versions of React Native will give you access to more APIs, views, developer tools +and other goodies. Because React Native projects are essentially made up of an Android project, an +iOS project and a JavaScript project, all combined under an npm package, upgrading can be rather +tricky. But we try to make it easy for you. Here's what you need to do to upgrade from an older +version of React Native:

1. Upgrade the react-native dependency #

Note the latest version of the react-native npm package from here (or use npm info react-native to check):

Now install that version of react-native in your project with npm install --save. For example, to upgrade to the version 0.34, in a terminal run:

$ npm install --save react-native@0.34

2. Upgrade your project templates #

The new npm package will likely contain updates to the files that are normally generated when you +run react-native init, like the iOS and the Android sub-projects. To get these latest changes, +run this in a terminal:

$ react-native upgrade

This will check your files against the latest template and perform the following:

  • If there is a new file in the template, it is simply created.
  • If a file in the template is identical to your file, it is skipped.
  • If a file is different in your project than the template, you will be prompted; you have options +to view a diff between your file and the template file, keep your file or overwrite it with the +template version. If you are unsure, press h to get a list of possible commands.

Manual Upgrades #

Some upgrades require manual steps, e.g. 0.13 to 0.14, or 0.28 to 0.29. Be sure to check the release notes when upgrading so that you can identify any manual changes your particular project may require.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/using-a-listview.html b/releases/0.37/docs/using-a-listview.html new file mode 100644 index 00000000000..af6f275393f --- /dev/null +++ b/releases/0.37/docs/using-a-listview.html @@ -0,0 +1,46 @@ +Using a ListView

Using a ListView #

The ListView component displays a vertically scrolling list of changing, but similarly structured, data.

ListView works well for long lists of data, where the number of items might change over time. Unlike the more generic ScrollView, the ListView only renders elements that are currently showing on the screen, not all the elements at once.

The ListView component requires two props: dataSource and renderRow. dataSource is the source of information for the list. renderRow takes one item from the source and returns a formatted component to render.

This example creates a simple ListView of hardcoded data. It first initializes the dataSource that will be used to populate the ListView. Each item in the dataSource is then rendered as a Text component. Finally it renders the ListView and all Text components.

A rowHasChanged function is required to use ListView. Here we just say a row has changed if the row we are on is not the same as the previous row.

import React, { Component } from 'react'; +import { AppRegistry, ListView, Text, View } from 'react-native'; + +class ListViewBasics extends Component { + // Initialize the hardcoded data + constructor(props) { + super(props); + const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2}); + this.state = { + dataSource: ds.cloneWithRows([ + 'John', 'Joel', 'James', 'Jimmy', 'Jackson', 'Jillian', 'Julie', 'Devin' + ]) + }; + } + render() { + return ( + <View style={{flex: 1, paddingTop: 22}}> + <ListView + dataSource={this.state.dataSource} + renderRow={(rowData) => <Text>{rowData}</Text>} + /> + </View> + ); + } +} + +// App registration and rendering +AppRegistry.registerComponent('ListViewBasics', () => ListViewBasics);

One of the most common uses for a ListView is displaying data that you fetch from a server. To do that, you will need to learn about networking in React Native.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/using-a-scrollview.html b/releases/0.37/docs/using-a-scrollview.html new file mode 100644 index 00000000000..e9d2015cd1b --- /dev/null +++ b/releases/0.37/docs/using-a-scrollview.html @@ -0,0 +1,65 @@ +Using a ScrollView

Using a ScrollView #

The ScrollView is a generic scrolling container that can host multiple components and views. The scrollable items need not be homogenous, and you can scroll both vertically and horizontally (by setting the horizontal property).

This example creates a vertical ScrollView with both images and text mixed together.

import React, { Component } from 'react'; +import { AppRegistry, ScrollView, Image, Text } from 'react-native' + +class IScrolledDownAndWhatHappenedNextShockedMe extends Component { + render() { + return( + <ScrollView> + <Text style={{fontSize:96}}>Scroll me plz</Text> + <Image source={require('./img/favicon.png')} /> + <Image source={require('./img/favicon.png')} /> + <Image source={require('./img/favicon.png')} /> + <Image source={require('./img/favicon.png')} /> + <Image source={require('./img/favicon.png')} /> + <Text style={{fontSize:96}}>If you like</Text> + <Image source={require('./img/favicon.png')} /> + <Image source={require('./img/favicon.png')} /> + <Image source={require('./img/favicon.png')} /> + <Image source={require('./img/favicon.png')} /> + <Image source={require('./img/favicon.png')} /> + <Text style={{fontSize:96}}>Scrolling down</Text> + <Image source={require('./img/favicon.png')} /> + <Image source={require('./img/favicon.png')} /> + <Image source={require('./img/favicon.png')} /> + <Image source={require('./img/favicon.png')} /> + <Image source={require('./img/favicon.png')} /> + <Text style={{fontSize:96}}>What's the best</Text> + <Image source={require('./img/favicon.png')} /> + <Image source={require('./img/favicon.png')} /> + <Image source={require('./img/favicon.png')} /> + <Image source={require('./img/favicon.png')} /> + <Image source={require('./img/favicon.png')} /> + <Text style={{fontSize:96}}>Framework around?</Text> + <Image source={require('./img/favicon.png')} /> + <Image source={require('./img/favicon.png')} /> + <Image source={require('./img/favicon.png')} /> + <Image source={require('./img/favicon.png')} /> + <Image source={require('./img/favicon.png')} /> + <Text style={{fontSize:80}}>React Native</Text> + </ScrollView> + ); + } +} + + +AppRegistry.registerComponent( + 'IScrolledDownAndWhatHappenedNextShockedMe', + () => IScrolledDownAndWhatHappenedNextShockedMe);

ScrollView works best to present a small amount of things of a limited size. All the elements and views of a ScrollView are rendered, even if they are not currently shown on the screen. If you have a long list of more items that can fit on the screen, you should use a ListView instead. So let's learn about the ListView next.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/using-navigators.html b/releases/0.37/docs/using-navigators.html new file mode 100644 index 00000000000..41979846de9 --- /dev/null +++ b/releases/0.37/docs/using-navigators.html @@ -0,0 +1,122 @@ +Using Navigators

Using Navigators #

Mobile apps rarely consist of just one screen. As soon as you add a second screen to your app, you will have to take into consideration how the user will navigate from one screen to the other.

You can use navigators to transition between multiple screens. These transitions can be typical side-to-side animations down a master/detail stack, or vertical modal popups.

Navigator #

React Native has several built-in navigation components, but for your first app you will probably want to use Navigator. It provides a JavaScript implementation of a navigation stack, so it works on both iOS and Android and is easy to customize.

Working with Scenes #

At this point you should feel comfortable rendering all sorts of components in your app, be it a simple View with Text inside, or a ScrollView with a list of Images. Together, these components make up a scene (another word for screen) in your app.

A scene is nothing other than a React component that is typically rendered full screen. This is in contrast to a Text, an Image, or even a custom SpinningBeachball component that is meant to be rendered as part of a screen. You may have already used one without realizing it - the "HelloWorldApp", the "FlexDirectionBasics", and the "ListViewBasics" components covered earlier in the tutorial are all examples of scenes.

For simplicity's sake, let's define a simple scene that displays a bit of text. We will come back to this scene later as we add navigation to our app. Create a new file called "MyScene.js" with the following contents:

import React, { Component } from 'react'; +import { View, Text, Navigator } from 'react-native'; + +export default class MyScene extends Component { + static get defaultProps() { + return { + title: 'MyScene' + }; + } + + render() { + return ( + <View> + <Text>Hi! My name is {this.props.title}.</Text> + </View> + ) + } +}

Notice the export default in front of the component declaration. This will export the component, and in turn allow other components to import it later on, like so:

import React, { Component } from 'react'; +import { AppRegistry } from 'react-native'; + +import MyScene from './MyScene'; + +class YoDawgApp extends Component { + render() { + return ( + <MyScene /> + ) + } +} + +AppRegistry.registerComponent('YoDawgApp', () => YoDawgApp);

We now have a simple app that renders your scene and nothing else. In this case, MyScene is a simple example of a reusable React component.

Using Navigator #

Enough about scenes, let's start navigating. We will start by rendering a Navigator, and then let the Navigator render the scene for you by passing in your own render function to its renderScene prop.

render() { + return ( + <Navigator + initialRoute={{ title: 'My Initial Scene', index: 0 }} + renderScene={(route, navigator) => { + return <MyScene title={route.title} /> + }} + /> + ); +}

Something you will encounter a lot when dealing with navigation is the concept of routes. A route is an object that contains information about a scene. It is used to provide all the context that the navigator's renderScene function needs to render a scene. It can have any number of keys to help distinguish your scene, and I happened to pick a single title key for the above example.

Pushing scenes onto the stack #

In order to transition to a new scene, you will need to learn about push and pop. These two methods are provided by the navigator object that is passed to your renderScene function above. They can be used, as you may have realized, to push and pop routes into your navigation stack.

navigator.push({ + title: 'Next Scene', + index: 1, +}); + +navigator.pop();

A more complete example that demonstrates the pushing and popping of routes. Edit your index*.js file to look something like this:

import React, { Component } from 'react'; +import { AppRegistry, Navigator } from 'react-native'; + +import MyScene from './MyScene'; + +class SimpleNavigationApp extends Component { + render() { + return ( + <Navigator + initialRoute={{ title: 'My Initial Scene', index: 0 }} + renderScene={(route, navigator) => + <MyScene + title={route.title} + + // Function to call when a new scene should be displayed + onForward={ () => { + const nextIndex = route.index + 1; + navigator.push({ + title: 'Scene ' + nextIndex, + index: nextIndex, + }); + }} + + // Function to call to go back to the previous scene + onBack={() => { + if (route.index > 0) { + navigator.pop(); + } + }} + /> + } + /> + ) + } +} + +AppRegistry.registerComponent('SimpleNavigationApp', () => SimpleNavigationApp);

And your MyScene.js to match this:

import React, { Component, PropTypes } from 'react'; +import { View, Text, TouchableHighlight } from 'react-native'; + +export default class MyScene extends Component { + render() { + return ( + <View> + <Text>Current Scene: { this.props.title }</Text> + <TouchableHighlight onPress={this.props.onForward}> + <Text>Tap me to load the next scene</Text> + </TouchableHighlight> + <TouchableHighlight onPress={this.props.onBack}> + <Text>Tap me to go back</Text> + </TouchableHighlight> + </View> + ) + } +} + +MyScene.propTypes = { + title: PropTypes.string.isRequired, + onForward: PropTypes.func.isRequired, + onBack: PropTypes.func.isRequired, +};

In this example, the MyScene component is passed the title of the current route via the title prop. It displays two tappable components that call the onForward and onBack functions passed through its props, which in turn will call navigator.push() and navigator.pop() as needed.

Check out the Navigator API reference for more Navigator code samples, or read through the Navigation guide for other examples of what you can do with navigators.

High Five! #

If you've gotten here by reading linearly through the tutorial, then you are a pretty impressive human being. Congratulations. Next, you might want to check out all the cool stuff the community does with React Native.

You can edit the content above on GitHub and send us a pull request!

\ No newline at end of file diff --git a/releases/0.37/docs/vibration.html b/releases/0.37/docs/vibration.html new file mode 100644 index 00000000000..429672e4fd9 --- /dev/null +++ b/releases/0.37/docs/vibration.html @@ -0,0 +1,135 @@ +Vibration

Vibration #

Methods #

static vibrate(pattern, repeat) #

static cancel(0) #

Stop vibration

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + StyleSheet, + View, + Text, + TouchableHighlight, + Vibration, + Platform, +} = ReactNative; + +exports.framework = 'React'; +exports.title = 'Vibration'; +exports.description = 'Vibration API'; + +var pattern, patternLiteral, patternDescription; +if (Platform.OS === 'android') { + pattern = [0, 500, 200, 500]; + patternLiteral = '[0, 500, 200, 500]'; + patternDescription = `${patternLiteral} +arg 0: duration to wait before turning the vibrator on. +arg with odd: vibration length. +arg with even: duration to wait before next vibration. +`; +} else { + pattern = [0, 1000, 2000, 3000]; + patternLiteral = '[0, 1000, 2000, 3000]'; + patternDescription = `${patternLiteral} +vibration length on iOS is fixed. +pattern controls durations BETWEEN each vibration only. + +arg 0: duration to wait before turning the vibrator on. +subsequent args: duration to wait before next vibrattion. +`; +} + +exports.examples = [ + { + title: 'Pattern Descriptions', + render() { + return ( + <View style={styles.wrapper}> + <Text>{patternDescription}</Text> + </View> + ); + }, + }, + { + title: 'Vibration.vibrate()', + render() { + return ( + <TouchableHighlight + style={styles.wrapper} + onPress={() => Vibration.vibrate()}> + <View style={styles.button}> + <Text>Vibrate</Text> + </View> + </TouchableHighlight> + ); + }, + }, + { + title: `Vibration.vibrate(${patternLiteral})`, + render() { + return ( + <TouchableHighlight + style={styles.wrapper} + onPress={() => Vibration.vibrate(pattern)}> + <View style={styles.button}> + <Text>Vibrate once</Text> + </View> + </TouchableHighlight> + ); + }, + }, + { + title: `Vibration.vibrate(${patternLiteral}, true)`, + render() { + return ( + <TouchableHighlight + style={styles.wrapper} + onPress={() => Vibration.vibrate(pattern, true)}> + <View style={styles.button}> + <Text>Vibrate until cancel</Text> + </View> + </TouchableHighlight> + ); + }, + }, + { + title: 'Vibration.cancel()', + render() { + return ( + <TouchableHighlight + style={styles.wrapper} + onPress={() => Vibration.cancel()}> + <View style={styles.button}> + <Text>Cancel</Text> + </View> + </TouchableHighlight> + ); + }, + }, +]; + +var styles = StyleSheet.create({ + wrapper: { + borderRadius: 5, + marginBottom: 5, + }, + button: { + backgroundColor: '#eeeeee', + padding: 10, + }, +});
\ No newline at end of file diff --git a/releases/0.37/docs/vibrationios.html b/releases/0.37/docs/vibrationios.html new file mode 100644 index 00000000000..2efb0b1b959 --- /dev/null +++ b/releases/0.37/docs/vibrationios.html @@ -0,0 +1,61 @@ +VibrationIOS

VibrationIOS #

NOTE: VibrationIOS is being deprecated. Use Vibration instead.

The Vibration API is exposed at VibrationIOS.vibrate(). On iOS, calling this +function will trigger a one second vibration. The vibration is asynchronous +so this method will return immediately.

There will be no effect on devices that do not support Vibration, eg. the iOS +simulator.

Vibration patterns are currently unsupported.

Methods #

static vibrate(0) #

@deprecated

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + StyleSheet, + View, + Text, + TouchableHighlight, + VibrationIOS +} = ReactNative; + +exports.framework = 'React'; +exports.title = 'VibrationIOS'; +exports.description = 'Vibration API for iOS'; +exports.examples = [{ + title: 'VibrationIOS.vibrate()', + render() { + return ( + <TouchableHighlight + style={styles.wrapper} + onPress={() => VibrationIOS.vibrate()}> + <View style={styles.button}> + <Text>Vibrate</Text> + </View> + </TouchableHighlight> + ); + }, +}]; + +var styles = StyleSheet.create({ + wrapper: { + borderRadius: 5, + marginBottom: 5, + }, + button: { + backgroundColor: '#eeeeee', + padding: 10, + }, +});
\ No newline at end of file diff --git a/releases/0.37/docs/view.html b/releases/0.37/docs/view.html new file mode 100644 index 00000000000..fd5b4eecb4d --- /dev/null +++ b/releases/0.37/docs/view.html @@ -0,0 +1,362 @@ +View

View #

The most fundamental component for building a UI, View is a container that supports layout with +flexbox, style, +some touch handling, and +accessibility controls. View maps directly to the +native view equivalent on whatever platform React Native is running on, whether that is a +UIView, <div>, android.view, etc.

View is designed to be nested inside other views and can have 0 to many children of any type.

This example creates a View that wraps two colored boxes and a text component in a row with +padding.

class ViewColoredBoxesWithText extends Component { + render() { + return ( + <View style={{flexDirection: 'row', height: 100, padding: 20}}> + <View style={{backgroundColor: 'blue', flex: 0.3}} /> + <View style={{backgroundColor: 'red', flex: 0.5}} /> + <Text>Hello World!</Text> + </View> + ); + } +}

Views are designed to be used with StyleSheet for clarity +and performance, although inline styles are also supported.

Synthetic Touch Events #

For View responder props (e.g., onResponderMove), the synthetic touch event passed to them +are of the following form:

  • nativeEvent
    • changedTouches - Array of all touch events that have changed since the last event.
    • identifier - The ID of the touch.
    • locationX - The X position of the touch, relative to the element.
    • locationY - The Y position of the touch, relative to the element.
    • pageX - The X position of the touch, relative to the root element.
    • pageY - The Y position of the touch, relative to the root element.
    • target - The node id of the element receiving the touch event.
    • timestamp - A time identifier for the touch, useful for velocity calculation.
    • touches - Array of all current touches on the screen.

Props #

accessibilityLabel node #

Overrides the text that's read by the screen reader when the user interacts +with the element. By default, the label is constructed by traversing all the +children and accumulating all the Text nodes separated by space.

accessible bool #

When true, indicates that the view is an accessibility element. By default, +all the touchable elements are accessible.

hitSlop {top: number, left: number, bottom: number, right: number} #

This defines how far a touch event can start away from the view. +Typical interface guidelines recommend touch targets that are at least +30 - 40 points/density-independent pixels.

For example, if a touchable view has a height of 20 the touchable height can be extended to +40 with hitSlop={{top: 10, bottom: 10, left: 0, right: 0}}

The touch area never extends past the parent view bounds and the Z-index +of sibling views always takes precedence if a touch hits two overlapping +views.

onAccessibilityTap function #

When accessible is true, the system will try to invoke this function +when the user performs accessibility tap gesture.

onLayout function #

Invoked on mount and layout changes with:

{nativeEvent: { layout: {x, y, width, height}}}

This event is fired immediately once the layout has been calculated, but +the new layout may not yet be reflected on the screen at the time the +event is received, especially if a layout animation is in progress.

onMagicTap function #

When accessible is true, the system will invoke this function when the +user performs the magic tap gesture.

onMoveShouldSetResponder function #

Does this view want to "claim" touch responsiveness? This is called for every touch move on +the View when it is not the responder.

View.props.onMoveShouldSetResponder: (event) => [true | false], where event is a +synthetic touch event as described above.

onMoveShouldSetResponderCapture function #

If a parent View wants to prevent a child View from becoming responder on a move, +it should have this handler which returns true.

View.props.onMoveShouldSetResponderCapture: (event) => [true | false], where event is a +synthetic touch event as described above.

onResponderGrant function #

The View is now responding for touch events. This is the time to highlight and show the user +what is happening.

View.props.onResponderGrant: (event) => {}, where event is a synthetic touch event as +described above.

onResponderMove function #

The user is moving their finger.

View.props.onResponderMove: (event) => {}, where event is a synthetic touch event as +described above.

onResponderReject function #

Another responder is already active and will not release it to that View asking to be +the responder.

View.props.onResponderReject: (event) => {}, where event is a synthetic touch event as +described above.

onResponderRelease function #

Fired at the end of the touch.

View.props.onResponderRelease: (event) => {}, where event is a synthetic touch event as +described above.

onResponderTerminate function #

The responder has been taken from the View. Might be taken by other views after a call to +onResponderTerminationRequest, or might be taken by the OS without asking (e.g., happens +with control center/ notification center on iOS)

View.props.onResponderTerminate: (event) => {}, where event is a synthetic touch event as +described above.

onResponderTerminationRequest function #

Some other View wants to become responder and is asking this View to release its +responder. Returning true allows its release.

View.props.onResponderTerminationRequest: (event) => {}, where event is a synthetic touch +event as described above.

onStartShouldSetResponder function #

Does this view want to become responder on the start of a touch?

View.props.onStartShouldSetResponder: (event) => [true | false], where event is a +synthetic touch event as described above.

onStartShouldSetResponderCapture function #

If a parent View wants to prevent a child View from becoming responder on a touch start, +it should have this handler which returns true.

View.props.onStartShouldSetResponderCapture: (event) => [true | false], where event is a +synthetic touch event as described above.

pointerEvents enum('box-none', 'none', 'box-only', 'auto') #

Controls whether the View can be the target of touch events.

  • 'auto': The View can be the target of touch events.
  • 'none': The View is never the target of touch events.
  • 'box-none': The View is never the target of touch events but it's +subviews can be. It behaves like if the view had the following classes +in CSS:
    .box-none { + pointer-events: none; +} +.box-none * { + pointer-events: all; +}
  • 'box-only': The view can be the target of touch events but it's +subviews cannot be. It behaves like if the view had the following classes +in CSS:
    .box-only { + pointer-events: all; +} +.box-only * { + pointer-events: none; +}

    Since pointerEvents does not affect layout/appearance, and we are +already deviating from the spec by adding additional modes, we opt to not +include pointerEvents on style. On some platforms, we would need to +implement it as a className anyways. Using style or not is an +implementation detail of the platform.

removeClippedSubviews bool #

This is a special performance property exposed by RCTView and is useful +for scrolling content when there are many subviews, most of which are +offscreen. For this property to be effective, it must be applied to a +view that contains many subviews that extend outside its bound. The +subviews must also have overflow: hidden, as should the containing view +(or one of its superviews).

style style #

backfaceVisibility enum('visible', 'hidden')
backgroundColor color
borderBottomColor color
borderBottomLeftRadius number
borderBottomRightRadius number
borderBottomWidth number
borderColor color
borderLeftColor color
borderLeftWidth number
borderRadius number
borderRightColor color
borderRightWidth number
borderStyle enum('solid', 'dotted', 'dashed')
borderTopColor color
borderTopLeftRadius number
borderTopRightRadius number
borderTopWidth number
borderWidth number
opacity number
androidelevation number

(Android-only) Sets the elevation of a view, using Android's underlying +elevation API. +This adds a drop shadow to the item and affects z-order for overlapping views. +Only supported on Android 5.0+, has no effect on earlier versions.

testID string #

Used to locate this view in end-to-end tests.

This disables the 'layout-only view removal' optimization for this view!

androidaccessibilityComponentType enum('none', 'button', 'radiobutton_checked', 'radiobutton_unchecked') #

Indicates to accessibility services to treat UI component like a +native one. Works for Android only.

Possible values are one of:

  • 'none'
  • 'button'
  • 'radiobutton_checked'
  • 'radiobutton_unchecked'

androidaccessibilityLiveRegion enum('none', 'polite', 'assertive') #

Indicates to accessibility services whether the user should be notified +when this view changes. Works for Android API >= 19 only. +Possible values:

  • 'none' - Accessibility services should not announce changes to this view.
  • 'polite'- Accessibility services should announce changes to this view.
  • 'assertive' - Accessibility services should interrupt ongoing speech to immediately announce changes to this view.

See the Android View docs +for reference.

androidcollapsable bool #

Views that are only used to layout their children or otherwise don't draw +anything may be automatically removed from the native hierarchy as an +optimization. Set this property to false to disable this optimization and +ensure that this View exists in the native view hierarchy.

androidimportantForAccessibility enum('auto', 'yes', 'no', 'no-hide-descendants') #

Controls how view is important for accessibility which is if it +fires accessibility events and if it is reported to accessibility services +that query the screen. Works for Android only.

Possible values:

  • 'auto' - The system determines whether the view is important for accessibility - +default (recommended).
  • 'yes' - The view is important for accessibility.
  • 'no' - The view is not important for accessibility.
  • 'no-hide-descendants' - The view is not important for accessibility, +nor are any of its descendant views.

See the Android importantForAccessibility docs +for reference.

androidneedsOffscreenAlphaCompositing bool #

Whether this View needs to rendered offscreen and composited with an alpha +in order to preserve 100% correct colors and blending behavior. The default +(false) falls back to drawing the component and its children with an alpha +applied to the paint used to draw each element instead of rendering the full +component offscreen and compositing it back with an alpha value. This default +may be noticeable and undesired in the case where the View you are setting +an opacity on has multiple overlapping elements (e.g. multiple overlapping +Views, or text and a background).

Rendering offscreen to preserve correct alpha behavior is extremely +expensive and hard to debug for non-native developers, which is why it is +not turned on by default. If you do need to enable this property for an +animation, consider combining it with renderToHardwareTextureAndroid if the +view contents are static (i.e. it doesn't need to be redrawn each frame). +If that property is enabled, this View will be rendered off-screen once, +saved in a hardware texture, and then composited onto the screen with an alpha +each frame without having to switch rendering targets on the GPU.

androidrenderToHardwareTextureAndroid bool #

Whether this View should render itself (and all of its children) into a +single hardware texture on the GPU.

On Android, this is useful for animations and interactions that only +modify opacity, rotation, translation, and/or scale: in those cases, the +view doesn't have to be redrawn and display lists don't need to be +re-executed. The texture can just be re-used and re-composited with +different parameters. The downside is that this can use up limited video +memory, so this prop should be set back to false at the end of the +interaction/animation.

iosaccessibilityTraits enum('none', 'button', 'link', 'header', 'search', 'image', 'selected', 'plays', 'key', 'text', 'summary', 'disabled', 'frequentUpdates', 'startsMedia', 'adjustable', 'allowsDirectInteraction', 'pageTurn'), [object Object] #

Provides additional traits to screen reader. By default no traits are +provided unless specified otherwise in element.

You can provide one trait or an array of many traits.

Possible values for AccessibilityTraits are:

  • 'none' - The element has no traits.
  • 'button' - The element should be treated as a button.
  • 'link' - The element should be treated as a link.
  • 'header' - The element is a header that divides content into sections.
  • 'search' - The element should be treated as a search field.
  • 'image' - The element should be treated as an image.
  • 'selected' - The element is selected.
  • 'plays' - The element plays sound.
  • 'key' - The element should be treated like a keyboard key.
  • 'text' - The element should be treated as text.
  • 'summary' - The element provides app summary information.
  • 'disabled' - The element is disabled.
  • 'frequentUpdates' - The element frequently changes its value.
  • 'startsMedia' - The element starts a media session.
  • 'adjustable' - The element allows adjustment over a range of values.
  • 'allowsDirectInteraction' - The element allows direct touch interaction for VoiceOver users.
  • 'pageTurn' - Informs VoiceOver that it should scroll to the next page when it finishes reading the contents of the element.

See the Accessibility guide +for more information.

iosshouldRasterizeIOS bool #

Whether this View should be rendered as a bitmap before compositing.

On iOS, this is useful for animations and interactions that do not +modify this component's dimensions nor its children; for example, when +translating the position of a static view, rasterization allows the +renderer to reuse a cached bitmap of a static view and quickly composite +it during each frame.

Rasterization incurs an off-screen drawing pass and the bitmap consumes +memory. Test and measure when using this property.

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + StyleSheet, + Text, + View, +} = ReactNative; +var TouchableWithoutFeedback = require('TouchableWithoutFeedback'); + +var styles = StyleSheet.create({ + box: { + backgroundColor: '#527FE4', + borderColor: '#000033', + borderWidth: 1, + }, + zIndex: { + justifyContent: 'space-around', + width: 100, + height: 50, + marginTop: -10, + }, +}); + +class ViewBorderStyleExample extends React.Component { + state = { + showBorder: true + }; + + render() { + return ( + <TouchableWithoutFeedback onPress={this._handlePress}> + <View> + <View style={{ + borderWidth: 1, + borderStyle: this.state.showBorder ? 'dashed' : null, + padding: 5 + }}> + <Text style={{fontSize: 11}}> + Dashed border style + </Text> + </View> + <View style={{ + marginTop: 5, + borderWidth: 1, + borderRadius: 5, + borderStyle: this.state.showBorder ? 'dotted' : null, + padding: 5 + }}> + <Text style={{fontSize: 11}}> + Dotted border style + </Text> + </View> + </View> + </TouchableWithoutFeedback> + ); + } + + _handlePress = () => { + this.setState({showBorder: !this.state.showBorder}); + }; +} + +class ZIndexExample extends React.Component { + state = { + flipped: false + }; + + render() { + const indices = this.state.flipped ? [-1, 0, 1, 2] : [2, 1, 0, -1]; + return ( + <TouchableWithoutFeedback onPress={this._handlePress}> + <View> + <Text style={{paddingBottom: 10}}>Tap to flip sorting order</Text> + <View style={[ + styles.zIndex, + {marginTop: 0, backgroundColor: '#E57373', zIndex: indices[0]} + ]}> + <Text>ZIndex {indices[0]}</Text> + </View> + <View style={[ + styles.zIndex, + {marginLeft: 50, backgroundColor: '#FFF176', zIndex: indices[1]} + ]}> + <Text>ZIndex {indices[1]}</Text> + </View> + <View style={[ + styles.zIndex, + {marginLeft: 100, backgroundColor: '#81C784', zIndex: indices[2]} + ]}> + <Text>ZIndex {indices[2]}</Text> + </View> + <View style={[ + styles.zIndex, + {marginLeft: 150, backgroundColor: '#64B5F6', zIndex: indices[3]} + ]}> + <Text>ZIndex {indices[3]}</Text> + </View> + </View> + </TouchableWithoutFeedback> + ); + } + + _handlePress = () => { + this.setState({flipped: !this.state.flipped}); + }; +} + +exports.title = '<View>'; +exports.description = 'Basic building block of all UI, examples that ' + + 'demonstrate some of the many styles available.'; + +exports.displayName = 'ViewExample'; +exports.examples = [ + { + title: 'Background Color', + render: function() { + return ( + <View style={{backgroundColor: '#527FE4', padding: 5}}> + <Text style={{fontSize: 11}}> + Blue background + </Text> + </View> + ); + }, + }, { + title: 'Border', + render: function() { + return ( + <View style={{borderColor: '#527FE4', borderWidth: 5, padding: 10}}> + <Text style={{fontSize: 11}}>5px blue border</Text> + </View> + ); + }, + }, { + title: 'Padding/Margin', + render: function() { + return ( + <View style={{borderColor: '#bb0000', borderWidth: 0.5}}> + <View style={[styles.box, {padding: 5}]}> + <Text style={{fontSize: 11}}>5px padding</Text> + </View> + <View style={[styles.box, {margin: 5}]}> + <Text style={{fontSize: 11}}>5px margin</Text> + </View> + <View style={[styles.box, {margin: 5, padding: 5, alignSelf: 'flex-start'}]}> + <Text style={{fontSize: 11}}> + 5px margin and padding, + </Text> + <Text style={{fontSize: 11}}> + widthAutonomous=true + </Text> + </View> + </View> + ); + }, + }, { + title: 'Border Radius', + render: function() { + return ( + <View style={{borderWidth: 0.5, borderRadius: 5, padding: 5}}> + <Text style={{fontSize: 11}}> + Too much use of `borderRadius` (especially large radii) on + anything which is scrolling may result in dropped frames. + Use sparingly. + </Text> + </View> + ); + }, + }, { + title: 'Border Style', + render: function() { + return <ViewBorderStyleExample />; + }, + }, { + title: 'Circle with Border Radius', + render: function() { + return ( + <View style={{borderRadius: 10, borderWidth: 1, width: 20, height: 20}} /> + ); + }, + }, { + title: 'Overflow', + render: function() { + return ( + <View style={{flexDirection: 'row'}}> + <View + style={{ + width: 95, + height: 10, + marginRight: 10, + marginBottom: 5, + overflow: 'hidden', + borderWidth: 0.5, + }}> + <View style={{width: 200, height: 20}}> + <Text>Overflow hidden</Text> + </View> + </View> + <View style={{width: 95, height: 10, marginBottom: 5, borderWidth: 0.5}}> + <View style={{width: 200, height: 20}}> + <Text>Overflow visible</Text> + </View> + </View> + </View> + ); + }, + }, { + title: 'Opacity', + render: function() { + return ( + <View> + <View style={{opacity: 0}}><Text>Opacity 0</Text></View> + <View style={{opacity: 0.1}}><Text>Opacity 0.1</Text></View> + <View style={{opacity: 0.3}}><Text>Opacity 0.3</Text></View> + <View style={{opacity: 0.5}}><Text>Opacity 0.5</Text></View> + <View style={{opacity: 0.7}}><Text>Opacity 0.7</Text></View> + <View style={{opacity: 0.9}}><Text>Opacity 0.9</Text></View> + <View style={{opacity: 1}}><Text>Opacity 1</Text></View> + </View> + ); + }, + }, { + title: 'ZIndex', + render: function() { + return <ZIndexExample />; + }, + }, +];
\ No newline at end of file diff --git a/releases/0.37/docs/viewpagerandroid.html b/releases/0.37/docs/viewpagerandroid.html new file mode 100644 index 00000000000..be04108e1f0 --- /dev/null +++ b/releases/0.37/docs/viewpagerandroid.html @@ -0,0 +1,322 @@ +ViewPagerAndroid

ViewPagerAndroid #

Container that allows to flip left and right between child views. Each +child view of the ViewPagerAndroid will be treated as a separate page +and will be stretched to fill the ViewPagerAndroid.

It is important all children are <View>s and not composite components. +You can set style properties like padding or backgroundColor for each +child.

Example:

render: function() { + return ( + <ViewPagerAndroid + style={styles.viewPager} + initialPage={0}> + <View style={styles.pageStyle}> + <Text>First page</Text> + </View> + <View style={styles.pageStyle}> + <Text>Second page</Text> + </View> + </ViewPagerAndroid> + ); +} + +... + +var styles = { + ... + pageStyle: { + alignItems: 'center', + padding: 20, + } +}

Props #

initialPage number #

Index of initial page that should be selected. Use setPage method to +update the page, and onPageSelected to monitor page changes

keyboardDismissMode enum('none', 'on-drag') #

Determines whether the keyboard gets dismissed in response to a drag. + - 'none' (the default), drags do not dismiss the keyboard. + - 'on-drag', the keyboard is dismissed when a drag begins.

onPageScroll function #

Executed when transitioning between pages (ether because of animation for +the requested page change or when user is swiping/dragging between pages) +The event.nativeEvent object for this callback will carry following data: + - position - index of first page from the left that is currently visible + - offset - value from range [0,1) describing stage between page transitions. + Value x means that (1 - x) fraction of the page at "position" index is + visible, and x fraction of the next page is visible.

onPageScrollStateChanged function #

Function called when the page scrolling state has changed. +The page scrolling state can be in 3 states: +- idle, meaning there is no interaction with the page scroller happening at the time +- dragging, meaning there is currently an interaction with the page scroller +- settling, meaning that there was an interaction with the page scroller, and the + page scroller is now finishing it's closing or opening animation

onPageSelected function #

This callback will be called once ViewPager finish navigating to selected page +(when user swipes between pages). The event.nativeEvent object passed to this +callback will have following fields: + - position - index of page that has been selected

pageMargin number #

Blank space to show between pages. This is only visible while scrolling, pages are still +edge-to-edge.

scrollEnabled bool #

When false, the content does not scroll. +The default value is true.

Type Definitions #

ViewPagerScrollState #

Type:
$Enum

Constants:
ValueDescription
idle
dragging
settling

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + Image, + StyleSheet, + Text, + TouchableWithoutFeedback, + TouchableOpacity, + View, + ViewPagerAndroid, +} = ReactNative; + +import type { ViewPagerScrollState } from 'ViewPagerAndroid'; + +var PAGES = 5; +var BGCOLOR = ['#fdc08e', '#fff6b9', '#99d1b7', '#dde5fe', '#f79273']; +var IMAGE_URIS = [ + 'http://apod.nasa.gov/apod/image/1410/20141008tleBaldridge001h990.jpg', + 'http://apod.nasa.gov/apod/image/1409/volcanicpillar_vetter_960.jpg', + 'http://apod.nasa.gov/apod/image/1409/m27_snyder_960.jpg', + 'http://apod.nasa.gov/apod/image/1409/PupAmulti_rot0.jpg', + 'http://apod.nasa.gov/apod/image/1510/lunareclipse_27Sep_beletskycrop4.jpg', +]; + +class LikeCount extends React.Component { + state = { + likes: 7, + }; + + onClick = () => { + this.setState({likes: this.state.likes + 1}); + }; + + render() { + var thumbsUp = '\uD83D\uDC4D'; + return ( + <View style={styles.likeContainer}> + <TouchableOpacity onPress={this.onClick} style={styles.likeButton}> + <Text style={styles.likesText}> + {thumbsUp + ' Like'} + </Text> + </TouchableOpacity> + <Text style={styles.likesText}> + {this.state.likes + ' likes'} + </Text> + </View> + ); + } +} + +class Button extends React.Component { + _handlePress = () => { + if (this.props.enabled && this.props.onPress) { + this.props.onPress(); + } + }; + + render() { + return ( + <TouchableWithoutFeedback onPress={this._handlePress}> + <View style={[styles.button, this.props.enabled ? {} : styles.buttonDisabled]}> + <Text style={styles.buttonText}>{this.props.text}</Text> + </View> + </TouchableWithoutFeedback> + ); + } +} + +class ProgressBar extends React.Component { + render() { + var fractionalPosition = (this.props.progress.position + this.props.progress.offset); + var progressBarSize = (fractionalPosition / (PAGES - 1)) * this.props.size; + return ( + <View style={[styles.progressBarContainer, {width: this.props.size}]}> + <View style={[styles.progressBar, {width: progressBarSize}]}/> + </View> + ); + } +} + +class ViewPagerAndroidExample extends React.Component { + static title = '<ViewPagerAndroid>'; + static description = 'Container that allows to flip left and right between child views.'; + + state = { + page: 0, + animationsAreEnabled: true, + scrollEnabled: true, + progress: { + position: 0, + offset: 0, + }, + }; + + onPageSelected = (e) => { + this.setState({page: e.nativeEvent.position}); + }; + + onPageScroll = (e) => { + this.setState({progress: e.nativeEvent}); + }; + + onPageScrollStateChanged = (state : ViewPagerScrollState) => { + this.setState({scrollState: state}); + }; + + move = (delta) => { + var page = this.state.page + delta; + this.go(page); + }; + + go = (page) => { + if (this.state.animationsAreEnabled) { + this.viewPager.setPage(page); + } else { + this.viewPager.setPageWithoutAnimation(page); + } + + this.setState({page}); + }; + + render() { + var pages = []; + for (var i = 0; i < PAGES; i++) { + var pageStyle = { + backgroundColor: BGCOLOR[i % BGCOLOR.length], + alignItems: 'center', + padding: 20, + }; + pages.push( + <View key={i} style={pageStyle} collapsable={false}> + <Image + style={styles.image} + source={{uri: IMAGE_URIS[i % BGCOLOR.length]}} + /> + <LikeCount /> + </View> + ); + } + var { page, animationsAreEnabled } = this.state; + return ( + <View style={styles.container}> + <ViewPagerAndroid + style={styles.viewPager} + initialPage={0} + scrollEnabled={this.state.scrollEnabled} + onPageScroll={this.onPageScroll} + onPageSelected={this.onPageSelected} + onPageScrollStateChanged={this.onPageScrollStateChanged} + pageMargin={10} + ref={viewPager => { this.viewPager = viewPager; }}> + {pages} + </ViewPagerAndroid> + <View style={styles.buttons}> + <Button + enabled={true} + text={this.state.scrollEnabled ? 'Scroll Enabled' : 'Scroll Disabled'} + onPress={() => this.setState({scrollEnabled: !this.state.scrollEnabled})} + /> + </View> + <View style={styles.buttons}> + { animationsAreEnabled ? + <Button + text="Turn off animations" + enabled={true} + onPress={() => this.setState({animationsAreEnabled: false})} + /> : + <Button + text="Turn animations back on" + enabled={true} + onPress={() => this.setState({animationsAreEnabled: true})} + /> } + <Text style={styles.scrollStateText}>ScrollState[ {this.state.scrollState} ]</Text> + </View> + <View style={styles.buttons}> + <Button text="Start" enabled={page > 0} onPress={() => this.go(0)}/> + <Button text="Prev" enabled={page > 0} onPress={() => this.move(-1)}/> + <Text style={styles.buttonText}>Page {page + 1} / {PAGES}</Text> + <ProgressBar size={100} progress={this.state.progress}/> + <Button text="Next" enabled={page < PAGES - 1} onPress={() => this.move(1)}/> + <Button text="Last" enabled={page < PAGES - 1} onPress={() => this.go(PAGES - 1)}/> + </View> + </View> + ); + } +} + +var styles = StyleSheet.create({ + buttons: { + flexDirection: 'row', + height: 30, + backgroundColor: 'black', + alignItems: 'center', + justifyContent: 'space-between', + }, + button: { + flex: 1, + width: 0, + margin: 5, + borderColor: 'gray', + borderWidth: 1, + backgroundColor: 'gray', + }, + buttonDisabled: { + backgroundColor: 'black', + opacity: 0.5, + }, + buttonText: { + color: 'white', + }, + scrollStateText: { + color: '#99d1b7', + }, + container: { + flex: 1, + backgroundColor: 'white', + }, + image: { + width: 300, + height: 200, + padding: 20, + }, + likeButton: { + backgroundColor: 'rgba(0, 0, 0, 0.1)', + borderColor: '#333333', + borderWidth: 1, + borderRadius: 5, + flex: 1, + margin: 8, + padding: 8, + }, + likeContainer: { + flexDirection: 'row', + }, + likesText: { + flex: 1, + fontSize: 18, + alignSelf: 'center', + }, + progressBarContainer: { + height: 10, + margin: 10, + borderColor: '#eeeeee', + borderWidth: 2, + }, + progressBar: { + alignSelf: 'flex-start', + flex: 1, + backgroundColor: '#eeeeee', + }, + viewPager: { + flex: 1, + }, +}); + +module.exports = ViewPagerAndroidExample;
\ No newline at end of file diff --git a/releases/0.37/docs/webview.html b/releases/0.37/docs/webview.html new file mode 100644 index 00000000000..3475c830e23 --- /dev/null +++ b/releases/0.37/docs/webview.html @@ -0,0 +1,481 @@ +WebView

WebView #

WebView renders web content in a native view.

import React, { Component } from 'react'; +import { WebView } from 'react-native'; + +class MyWeb extends Component { + render() { + return ( + <WebView + source={{uri: 'https://github.com/facebook/react-native'}} + style={{marginTop: 20}} + /> + ); + } +}

You can use this component to navigate back and forth in the web view's +history and configure various properties for the web content.

Props #

automaticallyAdjustContentInsets bool #

Controls whether to adjust the content inset for web views that are +placed behind a navigation bar, tab bar, or toolbar. The default value +is true.

contentInset {top: number, left: number, bottom: number, right: number} #

The amount by which the web view content is inset from the edges of +the scroll view. Defaults to {top: 0, left: 0, bottom: 0, right: 0}.

html string #

Deprecated

Use the source prop instead.

injectedJavaScript string #

Set this to provide JavaScript that will be injected into the web page +when the view loads.

mediaPlaybackRequiresUserAction bool #

Boolean that determines whether HTML5 audio and video requires the user +to tap them before they start playing. The default value is true.

onError function #

Function that is invoked when the WebView load fails.

onLoad function #

Function that is invoked when the WebView has finished loading.

onLoadEnd function #

Function that is invoked when the WebView load succeeds or fails.

onLoadStart function #

Function that is invoked when the WebView starts loading.

onMessage function #

A function that is invoked when the webview calls window.postMessage. +Setting this property will inject a postMessage global into your +webview, but will still call pre-existing values of postMessage.

window.postMessage accepts one argument, data, which will be +available on the event object, event.nativeEvent.data. data +must be a string.

onNavigationStateChange function #

Function that is invoked when the WebView loading starts or ends.

renderError function #

Function that returns a view to show if there's an error.

renderLoading function #

Function that returns a loading indicator.

scalesPageToFit bool #

Boolean that controls whether the web content is scaled to fit +the view and enables the user to change the scale. The default value +is true.

source {uri: string, method: string, headers: object, body: string}, {html: string, baseUrl: string}, number #

Loads static html or a uri (with optional headers) in the WebView.

startInLoadingState bool #

Boolean value that forces the WebView to show the loading view +on the first load.

style View#style #

The style to apply to the WebView.

url string #

Deprecated

Use the source prop instead.

androiddomStorageEnabled bool #

Boolean value to control whether DOM Storage is enabled. Used only in +Android.

androidjavaScriptEnabled bool #

Boolean value to enable JavaScript in the WebView. Used on Android only +as JavaScript is enabled by default on iOS. The default value is true.

androiduserAgent string #

Sets the user-agent for the WebView.

iosallowsInlineMediaPlayback bool #

Boolean that determines whether HTML5 videos play inline or use the +native full-screen controller. The default value is false.

NOTE : In order for video to play inline, not only does this +property need to be set to true, but the video element in the HTML +document must also include the webkit-playsinline attribute.

iosbounces bool #

Boolean value that determines whether the web view bounces +when it reaches the edge of the content. The default value is true.

iosdataDetectorTypes enum('phoneNumber', 'link', 'address', 'calendarEvent', 'none', 'all'), [object Object] #

Determines the types of data converted to clickable URLs in the web view’s content. +By default only phone numbers are detected.

You can provide one type or an array of many types.

Possible values for dataDetectorTypes are:

  • 'phoneNumber'
  • 'link'
  • 'address'
  • 'calendarEvent'
  • 'none'
  • 'all'

iosdecelerationRate ScrollView.propTypes.decelerationRate #

A floating-point number that determines how quickly the scroll view +decelerates after the user lifts their finger. You may also use the +string shortcuts "normal" and "fast" which match the underlying iOS +settings for UIScrollViewDecelerationRateNormal and +UIScrollViewDecelerationRateFast respectively:

  • normal: 0.998
  • fast: 0.99 (the default for iOS web view)

iosonShouldStartLoadWithRequest function #

Function that allows custom handling of any web view requests. Return +true from the function to continue loading the request and false +to stop loading.

iosscrollEnabled bool #

Boolean value that determines whether scrolling is enabled in the +WebView. The default value is true.

You can edit the content above on GitHub and send us a pull request!

Examples #

Edit on GitHub
'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + StyleSheet, + Text, + TextInput, + TouchableWithoutFeedback, + TouchableOpacity, + View, + WebView +} = ReactNative; + +var HEADER = '#3b5998'; +var BGWASH = 'rgba(255,255,255,0.8)'; +var DISABLED_WASH = 'rgba(255,255,255,0.25)'; + +var TEXT_INPUT_REF = 'urlInput'; +var WEBVIEW_REF = 'webview'; +var DEFAULT_URL = 'https://m.facebook.com'; + +class WebViewExample extends React.Component { + state = { + url: DEFAULT_URL, + status: 'No Page Loaded', + backButtonEnabled: false, + forwardButtonEnabled: false, + loading: true, + scalesPageToFit: true, + }; + + inputText = ''; + + handleTextInputChange = (event) => { + var url = event.nativeEvent.text; + if (!/^[a-zA-Z-_]+:/.test(url)) { + url = 'http://' + url; + } + this.inputText = url; + }; + + render() { + this.inputText = this.state.url; + + return ( + <View style={[styles.container]}> + <View style={[styles.addressBarRow]}> + <TouchableOpacity + onPress={this.goBack} + style={this.state.backButtonEnabled ? styles.navButton : styles.disabledButton}> + <Text> + {'<'} + </Text> + </TouchableOpacity> + <TouchableOpacity + onPress={this.goForward} + style={this.state.forwardButtonEnabled ? styles.navButton : styles.disabledButton}> + <Text> + {'>'} + </Text> + </TouchableOpacity> + <TextInput + ref={TEXT_INPUT_REF} + autoCapitalize="none" + defaultValue={this.state.url} + onSubmitEditing={this.onSubmitEditing} + onChange={this.handleTextInputChange} + clearButtonMode="while-editing" + style={styles.addressBarTextInput} + /> + <TouchableOpacity onPress={this.pressGoButton}> + <View style={styles.goButton}> + <Text> + Go! + </Text> + </View> + </TouchableOpacity> + </View> + <WebView + ref={WEBVIEW_REF} + automaticallyAdjustContentInsets={false} + style={styles.webView} + source={{uri: this.state.url}} + javaScriptEnabled={true} + domStorageEnabled={true} + decelerationRate="normal" + onNavigationStateChange={this.onNavigationStateChange} + onShouldStartLoadWithRequest={this.onShouldStartLoadWithRequest} + startInLoadingState={true} + scalesPageToFit={this.state.scalesPageToFit} + /> + <View style={styles.statusBar}> + <Text style={styles.statusBarText}>{this.state.status}</Text> + </View> + </View> + ); + } + + goBack = () => { + this.refs[WEBVIEW_REF].goBack(); + }; + + goForward = () => { + this.refs[WEBVIEW_REF].goForward(); + }; + + reload = () => { + this.refs[WEBVIEW_REF].reload(); + }; + + onShouldStartLoadWithRequest = (event) => { + // Implement any custom loading logic here, don't forget to return! + return true; + }; + + onNavigationStateChange = (navState) => { + this.setState({ + backButtonEnabled: navState.canGoBack, + forwardButtonEnabled: navState.canGoForward, + url: navState.url, + status: navState.title, + loading: navState.loading, + scalesPageToFit: true + }); + }; + + onSubmitEditing = (event) => { + this.pressGoButton(); + }; + + pressGoButton = () => { + var url = this.inputText.toLowerCase(); + if (url === this.state.url) { + this.reload(); + } else { + this.setState({ + url: url, + }); + } + // dismiss keyboard + this.refs[TEXT_INPUT_REF].blur(); + }; +} + +class Button extends React.Component { + _handlePress = () => { + if (this.props.enabled !== false && this.props.onPress) { + this.props.onPress(); + } + }; + + render() { + return ( + <TouchableWithoutFeedback onPress={this._handlePress}> + <View style={styles.button}> + <Text>{this.props.text}</Text> + </View> + </TouchableWithoutFeedback> + ); + } +} + +class ScaledWebView extends React.Component { + state = { + scalingEnabled: true, + }; + + render() { + return ( + <View> + <WebView + style={{ + backgroundColor: BGWASH, + height: 200, + }} + source={{uri: 'https://facebook.github.io/react/'}} + scalesPageToFit={this.state.scalingEnabled} + /> + <View style={styles.buttons}> + { this.state.scalingEnabled ? + <Button + text="Scaling:ON" + enabled={true} + onPress={() => this.setState({scalingEnabled: false})} + /> : + <Button + text="Scaling:OFF" + enabled={true} + onPress={() => this.setState({scalingEnabled: true})} + /> } + </View> + </View> + ); + } +} + +class MessagingTest extends React.Component { + webview = null + + state = { + messagesReceivedFromWebView: 0, + message: '', + } + + onMessage = e => this.setState({ + messagesReceivedFromWebView: this.state.messagesReceivedFromWebView + 1, + message: e.nativeEvent.data, + }) + + postMessage = () => { + if (this.webview) { + this.webview.postMessage('"Hello" from React Native!'); + } + } + + render(): ReactElement<any> { + const {messagesReceivedFromWebView, message} = this.state; + + return ( + <View style={[styles.container, { height: 200 }]}> + <View style={styles.container}> + <Text>Messages received from web view: {messagesReceivedFromWebView}</Text> + <Text>{message || '(No message)'}</Text> + <View style={styles.buttons}> + <Button text="Send Message to Web View" enabled onPress={this.postMessage} /> + </View> + </View> + <View style={styles.container}> + <WebView + ref={webview => { this.webview = webview; }} + style={{ + backgroundColor: BGWASH, + height: 100, + }} + source={require('./messagingtest.html')} + onMessage={this.onMessage} + /> + </View> + </View> + ); + } +} + +var styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: HEADER, + }, + addressBarRow: { + flexDirection: 'row', + padding: 8, + }, + webView: { + backgroundColor: BGWASH, + height: 350, + }, + addressBarTextInput: { + backgroundColor: BGWASH, + borderColor: 'transparent', + borderRadius: 3, + borderWidth: 1, + height: 24, + paddingLeft: 10, + paddingTop: 3, + paddingBottom: 3, + flex: 1, + fontSize: 14, + }, + navButton: { + width: 20, + padding: 3, + marginRight: 3, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: BGWASH, + borderColor: 'transparent', + borderRadius: 3, + }, + disabledButton: { + width: 20, + padding: 3, + marginRight: 3, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: DISABLED_WASH, + borderColor: 'transparent', + borderRadius: 3, + }, + goButton: { + height: 24, + padding: 3, + marginLeft: 8, + alignItems: 'center', + backgroundColor: BGWASH, + borderColor: 'transparent', + borderRadius: 3, + alignSelf: 'stretch', + }, + statusBar: { + flexDirection: 'row', + alignItems: 'center', + paddingLeft: 5, + height: 22, + }, + statusBarText: { + color: 'white', + fontSize: 13, + }, + spinner: { + width: 20, + marginRight: 6, + }, + buttons: { + flexDirection: 'row', + height: 30, + backgroundColor: 'black', + alignItems: 'center', + justifyContent: 'space-between', + }, + button: { + flex: 0.5, + width: 0, + margin: 5, + borderColor: 'gray', + borderWidth: 1, + backgroundColor: 'gray', + }, +}); + +const HTML = ` +<!DOCTYPE html>\n +<html> + <head> + <title>Hello Static World</title> + <meta http-equiv="content-type" content="text/html; charset=utf-8"> + <meta name="viewport" content="width=320, user-scalable=no"> + <style type="text/css"> + body { + margin: 0; + padding: 0; + font: 62.5% arial, sans-serif; + background: #ccc; + } + h1 { + padding: 45px; + margin: 0; + text-align: center; + color: #33f; + } + </style> + </head> + <body> + <h1>Hello Static World</h1> + </body> +</html> +`; + +exports.displayName = (undefined: ?string); +exports.title = '<WebView>'; +exports.description = 'Base component to display web content'; +exports.examples = [ + { + title: 'Simple Browser', + render(): React.Element<any> { return <WebViewExample />; } + }, + { + title: 'Scale Page to Fit', + render(): React.Element<any> { return <ScaledWebView/>; } + }, + { + title: 'Bundled HTML', + render(): React.Element<any> { + return ( + <WebView + style={{ + backgroundColor: BGWASH, + height: 100, + }} + source={require('./helloworld.html')} + scalesPageToFit={true} + /> + ); + } + }, + { + title: 'Static HTML', + render(): React.Element<any> { + return ( + <WebView + style={{ + backgroundColor: BGWASH, + height: 100, + }} + source={{html: HTML}} + scalesPageToFit={true} + /> + ); + } + }, + { + title: 'POST Test', + render(): React.Element<any> { + return ( + <WebView + style={{ + backgroundColor: BGWASH, + height: 100, + }} + source={{ + uri: 'http://www.posttestserver.com/post.php', + method: 'POST', + body: 'foo=bar&bar=foo' + }} + scalesPageToFit={false} + /> + ); + } + }, + { + title: 'Mesaging Test', + render(): ReactElement<any> { return <MessagingTest />; } + } +];
\ No newline at end of file diff --git a/releases/0.37/img/AddToBuildPhases.png b/releases/0.37/img/AddToBuildPhases.png new file mode 100644 index 00000000000..7b83937020d Binary files /dev/null and b/releases/0.37/img/AddToBuildPhases.png differ diff --git a/releases/0.37/img/AddToLibraries.png b/releases/0.37/img/AddToLibraries.png new file mode 100644 index 00000000000..652cdfd1cfd Binary files /dev/null and b/releases/0.37/img/AddToLibraries.png differ diff --git a/releases/0.37/img/AddToSearchPaths.png b/releases/0.37/img/AddToSearchPaths.png new file mode 100644 index 00000000000..ccbe819ba78 Binary files /dev/null and b/releases/0.37/img/AddToSearchPaths.png differ diff --git a/releases/0.37/img/AndroidDevServerDialog.png b/releases/0.37/img/AndroidDevServerDialog.png new file mode 100644 index 00000000000..9fd5536acdf Binary files /dev/null and b/releases/0.37/img/AndroidDevServerDialog.png differ diff --git a/releases/0.37/img/AndroidDevSettings.png b/releases/0.37/img/AndroidDevSettings.png new file mode 100644 index 00000000000..0c214571f7e Binary files /dev/null and b/releases/0.37/img/AndroidDevSettings.png differ diff --git a/releases/0.37/img/AndroidDeveloperMenu.png b/releases/0.37/img/AndroidDeveloperMenu.png new file mode 100644 index 00000000000..2fae4ccaa23 Binary files /dev/null and b/releases/0.37/img/AndroidDeveloperMenu.png differ diff --git a/releases/0.37/img/AndroidSDK1.png b/releases/0.37/img/AndroidSDK1.png new file mode 100644 index 00000000000..9b9add740ed Binary files /dev/null and b/releases/0.37/img/AndroidSDK1.png differ diff --git a/releases/0.37/img/AndroidSDK2.png b/releases/0.37/img/AndroidSDK2.png new file mode 100644 index 00000000000..a554e07d48e Binary files /dev/null and b/releases/0.37/img/AndroidSDK2.png differ diff --git a/releases/0.37/img/AnimationExperimentalOpacity.gif b/releases/0.37/img/AnimationExperimentalOpacity.gif new file mode 100644 index 00000000000..cdc79022b7e Binary files /dev/null and b/releases/0.37/img/AnimationExperimentalOpacity.gif differ diff --git a/releases/0.37/img/AnimationExperimentalScaleXY.gif b/releases/0.37/img/AnimationExperimentalScaleXY.gif new file mode 100644 index 00000000000..850cfd18c2b Binary files /dev/null and b/releases/0.37/img/AnimationExperimentalScaleXY.gif differ diff --git a/releases/0.37/img/CreateAVD.png b/releases/0.37/img/CreateAVD.png new file mode 100644 index 00000000000..5a3d49445f6 Binary files /dev/null and b/releases/0.37/img/CreateAVD.png differ diff --git a/releases/0.37/img/DeveloperMenu.png b/releases/0.37/img/DeveloperMenu.png new file mode 100644 index 00000000000..bb295444ee1 Binary files /dev/null and b/releases/0.37/img/DeveloperMenu.png differ diff --git a/releases/0.37/img/EmbeddedAppAndroid.png b/releases/0.37/img/EmbeddedAppAndroid.png new file mode 100644 index 00000000000..126ba2d7442 Binary files /dev/null and b/releases/0.37/img/EmbeddedAppAndroid.png differ diff --git a/releases/0.37/img/EmbeddedAppContainerViewExample.png b/releases/0.37/img/EmbeddedAppContainerViewExample.png new file mode 100644 index 00000000000..6130dfb1e10 Binary files /dev/null and b/releases/0.37/img/EmbeddedAppContainerViewExample.png differ diff --git a/releases/0.37/img/EmbeddedAppExample.png b/releases/0.37/img/EmbeddedAppExample.png new file mode 100644 index 00000000000..fefd3069ddf Binary files /dev/null and b/releases/0.37/img/EmbeddedAppExample.png differ diff --git a/releases/0.37/img/LayoutAnimationExample.gif b/releases/0.37/img/LayoutAnimationExample.gif new file mode 100644 index 00000000000..68fcfcee4d7 Binary files /dev/null and b/releases/0.37/img/LayoutAnimationExample.gif differ diff --git a/releases/0.37/img/NavigationStack-Navigator.gif b/releases/0.37/img/NavigationStack-Navigator.gif new file mode 100644 index 00000000000..c1f8313996c Binary files /dev/null and b/releases/0.37/img/NavigationStack-Navigator.gif differ diff --git a/releases/0.37/img/NavigationStack-NavigatorIOS.gif b/releases/0.37/img/NavigationStack-NavigatorIOS.gif new file mode 100644 index 00000000000..c1d56a1f555 Binary files /dev/null and b/releases/0.37/img/NavigationStack-NavigatorIOS.gif differ diff --git a/releases/0.37/img/ObjectObserveError.png b/releases/0.37/img/ObjectObserveError.png new file mode 100644 index 00000000000..3634969fdfc Binary files /dev/null and b/releases/0.37/img/ObjectObserveError.png differ diff --git a/releases/0.37/img/Rebound.gif b/releases/0.37/img/Rebound.gif new file mode 100644 index 00000000000..03716633818 Binary files /dev/null and b/releases/0.37/img/Rebound.gif differ diff --git a/releases/0.37/img/ReboundExample.png b/releases/0.37/img/ReboundExample.png new file mode 100644 index 00000000000..db33e5f8a85 Binary files /dev/null and b/releases/0.37/img/ReboundExample.png differ diff --git a/releases/0.37/img/ReboundImage.gif b/releases/0.37/img/ReboundImage.gif new file mode 100644 index 00000000000..9c1da74f150 Binary files /dev/null and b/releases/0.37/img/ReboundImage.gif differ diff --git a/releases/0.37/img/StaticImageAssets.png b/releases/0.37/img/StaticImageAssets.png new file mode 100644 index 00000000000..e79acdc1b6f Binary files /dev/null and b/releases/0.37/img/StaticImageAssets.png differ diff --git a/releases/0.37/img/SystraceBadCreateUI.png b/releases/0.37/img/SystraceBadCreateUI.png new file mode 100644 index 00000000000..813b1014aea Binary files /dev/null and b/releases/0.37/img/SystraceBadCreateUI.png differ diff --git a/releases/0.37/img/SystraceBadJS.png b/releases/0.37/img/SystraceBadJS.png new file mode 100644 index 00000000000..c67c840ac92 Binary files /dev/null and b/releases/0.37/img/SystraceBadJS.png differ diff --git a/releases/0.37/img/SystraceBadJS2.png b/releases/0.37/img/SystraceBadJS2.png new file mode 100644 index 00000000000..de972c6494a Binary files /dev/null and b/releases/0.37/img/SystraceBadJS2.png differ diff --git a/releases/0.37/img/SystraceBadUI.png b/releases/0.37/img/SystraceBadUI.png new file mode 100644 index 00000000000..ebd9faf7d66 Binary files /dev/null and b/releases/0.37/img/SystraceBadUI.png differ diff --git a/releases/0.37/img/SystraceExample.png b/releases/0.37/img/SystraceExample.png new file mode 100644 index 00000000000..521ee1635c8 Binary files /dev/null and b/releases/0.37/img/SystraceExample.png differ diff --git a/releases/0.37/img/SystraceHighlightVSync.png b/releases/0.37/img/SystraceHighlightVSync.png new file mode 100644 index 00000000000..dc7595a3611 Binary files /dev/null and b/releases/0.37/img/SystraceHighlightVSync.png differ diff --git a/releases/0.37/img/SystraceJSThreadExample.png b/releases/0.37/img/SystraceJSThreadExample.png new file mode 100644 index 00000000000..736af7d0562 Binary files /dev/null and b/releases/0.37/img/SystraceJSThreadExample.png differ diff --git a/releases/0.37/img/SystraceNativeModulesThreadExample.png b/releases/0.37/img/SystraceNativeModulesThreadExample.png new file mode 100644 index 00000000000..7e919f24c22 Binary files /dev/null and b/releases/0.37/img/SystraceNativeModulesThreadExample.png differ diff --git a/releases/0.37/img/SystraceRenderThreadExample.png b/releases/0.37/img/SystraceRenderThreadExample.png new file mode 100644 index 00000000000..2fa05bdbddd Binary files /dev/null and b/releases/0.37/img/SystraceRenderThreadExample.png differ diff --git a/releases/0.37/img/SystraceUIThreadExample.png b/releases/0.37/img/SystraceUIThreadExample.png new file mode 100644 index 00000000000..4883e85c075 Binary files /dev/null and b/releases/0.37/img/SystraceUIThreadExample.png differ diff --git a/releases/0.37/img/SystraceWellBehaved.png b/releases/0.37/img/SystraceWellBehaved.png new file mode 100644 index 00000000000..95408738f74 Binary files /dev/null and b/releases/0.37/img/SystraceWellBehaved.png differ diff --git a/releases/0.37/img/TutorialFinal.png b/releases/0.37/img/TutorialFinal.png new file mode 100644 index 00000000000..2f05b13e2ea Binary files /dev/null and b/releases/0.37/img/TutorialFinal.png differ diff --git a/releases/0.37/img/TutorialFinal2.png b/releases/0.37/img/TutorialFinal2.png new file mode 100644 index 00000000000..75ec47c54ea Binary files /dev/null and b/releases/0.37/img/TutorialFinal2.png differ diff --git a/releases/0.37/img/TutorialMock.png b/releases/0.37/img/TutorialMock.png new file mode 100644 index 00000000000..6a267d08995 Binary files /dev/null and b/releases/0.37/img/TutorialMock.png differ diff --git a/releases/0.37/img/TutorialMock2.png b/releases/0.37/img/TutorialMock2.png new file mode 100644 index 00000000000..94c7f656b1d Binary files /dev/null and b/releases/0.37/img/TutorialMock2.png differ diff --git a/releases/0.37/img/TutorialSingleFetched.png b/releases/0.37/img/TutorialSingleFetched.png new file mode 100644 index 00000000000..914cb8833b9 Binary files /dev/null and b/releases/0.37/img/TutorialSingleFetched.png differ diff --git a/releases/0.37/img/TutorialSingleFetched2.png b/releases/0.37/img/TutorialSingleFetched2.png new file mode 100644 index 00000000000..c7dfd69e8d3 Binary files /dev/null and b/releases/0.37/img/TutorialSingleFetched2.png differ diff --git a/releases/0.37/img/TutorialStyledMock.png b/releases/0.37/img/TutorialStyledMock.png new file mode 100644 index 00000000000..48fb1c5e732 Binary files /dev/null and b/releases/0.37/img/TutorialStyledMock.png differ diff --git a/releases/0.37/img/TutorialStyledMock2.png b/releases/0.37/img/TutorialStyledMock2.png new file mode 100644 index 00000000000..16e99c2028f Binary files /dev/null and b/releases/0.37/img/TutorialStyledMock2.png differ diff --git a/releases/0.37/img/TweenState.gif b/releases/0.37/img/TweenState.gif new file mode 100644 index 00000000000..84f34d2ec8f Binary files /dev/null and b/releases/0.37/img/TweenState.gif differ diff --git a/releases/0.37/img/Warning.png b/releases/0.37/img/Warning.png new file mode 100644 index 00000000000..ceeb6edf2c6 Binary files /dev/null and b/releases/0.37/img/Warning.png differ diff --git a/releases/0.37/img/alertIOS.png b/releases/0.37/img/alertIOS.png new file mode 100644 index 00000000000..b35a2a457f9 Binary files /dev/null and b/releases/0.37/img/alertIOS.png differ diff --git a/releases/0.37/img/author.png b/releases/0.37/img/author.png new file mode 100644 index 00000000000..deff4c45562 Binary files /dev/null and b/releases/0.37/img/author.png differ diff --git a/releases/0.37/img/buttonExample.png b/releases/0.37/img/buttonExample.png new file mode 100644 index 00000000000..40ce923b45b Binary files /dev/null and b/releases/0.37/img/buttonExample.png differ diff --git a/releases/0.37/img/chrome_breakpoint.png b/releases/0.37/img/chrome_breakpoint.png new file mode 100644 index 00000000000..d37c9538b03 Binary files /dev/null and b/releases/0.37/img/chrome_breakpoint.png differ diff --git a/releases/0.37/img/favicon.png b/releases/0.37/img/favicon.png new file mode 100644 index 00000000000..22c120d09b2 Binary files /dev/null and b/releases/0.37/img/favicon.png differ diff --git a/releases/0.37/img/header_logo.png b/releases/0.37/img/header_logo.png new file mode 100644 index 00000000000..8858ffa29da Binary files /dev/null and b/releases/0.37/img/header_logo.png differ diff --git a/releases/0.37/img/opengraph.png b/releases/0.37/img/opengraph.png new file mode 100644 index 00000000000..437433e9afa Binary files /dev/null and b/releases/0.37/img/opengraph.png differ diff --git a/releases/0.37/img/oss_logo.png b/releases/0.37/img/oss_logo.png new file mode 100644 index 00000000000..8183e289b13 Binary files /dev/null and b/releases/0.37/img/oss_logo.png differ diff --git a/releases/0.37/img/react-native-add-react-native-integration-example-high-scores.png b/releases/0.37/img/react-native-add-react-native-integration-example-high-scores.png new file mode 100644 index 00000000000..6d07707ba86 Binary files /dev/null and b/releases/0.37/img/react-native-add-react-native-integration-example-high-scores.png differ diff --git a/releases/0.37/img/react-native-add-react-native-integration-example-home-screen.png b/releases/0.37/img/react-native-add-react-native-integration-example-home-screen.png new file mode 100644 index 00000000000..2b1b8b28735 Binary files /dev/null and b/releases/0.37/img/react-native-add-react-native-integration-example-home-screen.png differ diff --git a/releases/0.37/img/react-native-add-react-native-integration-link.png b/releases/0.37/img/react-native-add-react-native-integration-link.png new file mode 100644 index 00000000000..3d89eaf020a Binary files /dev/null and b/releases/0.37/img/react-native-add-react-native-integration-link.png differ diff --git a/releases/0.37/img/react-native-add-react-native-integration-wire-up.png b/releases/0.37/img/react-native-add-react-native-integration-wire-up.png new file mode 100644 index 00000000000..43d2add57ba Binary files /dev/null and b/releases/0.37/img/react-native-add-react-native-integration-wire-up.png differ diff --git a/releases/0.37/img/react-native-android-sdk-environment-variable-windows.png b/releases/0.37/img/react-native-android-sdk-environment-variable-windows.png new file mode 100644 index 00000000000..6b3d9810a53 Binary files /dev/null and b/releases/0.37/img/react-native-android-sdk-environment-variable-windows.png differ diff --git a/releases/0.37/img/react-native-android-studio-additional-installs-linux.png b/releases/0.37/img/react-native-android-studio-additional-installs-linux.png new file mode 100644 index 00000000000..3a0eda555b5 Binary files /dev/null and b/releases/0.37/img/react-native-android-studio-additional-installs-linux.png differ diff --git a/releases/0.37/img/react-native-android-studio-additional-installs.png b/releases/0.37/img/react-native-android-studio-additional-installs.png new file mode 100644 index 00000000000..de32a09e007 Binary files /dev/null and b/releases/0.37/img/react-native-android-studio-additional-installs.png differ diff --git a/releases/0.37/img/react-native-android-studio-android-sdk-build-tools-linux.png b/releases/0.37/img/react-native-android-studio-android-sdk-build-tools-linux.png new file mode 100644 index 00000000000..10391c74108 Binary files /dev/null and b/releases/0.37/img/react-native-android-studio-android-sdk-build-tools-linux.png differ diff --git a/releases/0.37/img/react-native-android-studio-android-sdk-build-tools-windows.png b/releases/0.37/img/react-native-android-studio-android-sdk-build-tools-windows.png new file mode 100644 index 00000000000..600ef3a428a Binary files /dev/null and b/releases/0.37/img/react-native-android-studio-android-sdk-build-tools-windows.png differ diff --git a/releases/0.37/img/react-native-android-studio-android-sdk-build-tools.png b/releases/0.37/img/react-native-android-studio-android-sdk-build-tools.png new file mode 100644 index 00000000000..a1d80be7a57 Binary files /dev/null and b/releases/0.37/img/react-native-android-studio-android-sdk-build-tools.png differ diff --git a/releases/0.37/img/react-native-android-studio-android-sdk-platforms-linux.png b/releases/0.37/img/react-native-android-studio-android-sdk-platforms-linux.png new file mode 100644 index 00000000000..8c43a493867 Binary files /dev/null and b/releases/0.37/img/react-native-android-studio-android-sdk-platforms-linux.png differ diff --git a/releases/0.37/img/react-native-android-studio-android-sdk-platforms-windows.png b/releases/0.37/img/react-native-android-studio-android-sdk-platforms-windows.png new file mode 100644 index 00000000000..a5cf1758d28 Binary files /dev/null and b/releases/0.37/img/react-native-android-studio-android-sdk-platforms-windows.png differ diff --git a/releases/0.37/img/react-native-android-studio-android-sdk-platforms.png b/releases/0.37/img/react-native-android-studio-android-sdk-platforms.png new file mode 100644 index 00000000000..34407b136d6 Binary files /dev/null and b/releases/0.37/img/react-native-android-studio-android-sdk-platforms.png differ diff --git a/releases/0.37/img/react-native-android-studio-avd-linux.png b/releases/0.37/img/react-native-android-studio-avd-linux.png new file mode 100644 index 00000000000..de5f2542095 Binary files /dev/null and b/releases/0.37/img/react-native-android-studio-avd-linux.png differ diff --git a/releases/0.37/img/react-native-android-studio-avd-windows.png b/releases/0.37/img/react-native-android-studio-avd-windows.png new file mode 100644 index 00000000000..ddc8f4790a7 Binary files /dev/null and b/releases/0.37/img/react-native-android-studio-avd-windows.png differ diff --git a/releases/0.37/img/react-native-android-studio-avd.png b/releases/0.37/img/react-native-android-studio-avd.png new file mode 100644 index 00000000000..74c053b6cf9 Binary files /dev/null and b/releases/0.37/img/react-native-android-studio-avd.png differ diff --git a/releases/0.37/img/react-native-android-studio-configure-sdk-linux.png b/releases/0.37/img/react-native-android-studio-configure-sdk-linux.png new file mode 100644 index 00000000000..8bb9d5fea6f Binary files /dev/null and b/releases/0.37/img/react-native-android-studio-configure-sdk-linux.png differ diff --git a/releases/0.37/img/react-native-android-studio-configure-sdk-windows.png b/releases/0.37/img/react-native-android-studio-configure-sdk-windows.png new file mode 100644 index 00000000000..1adf5cbdb80 Binary files /dev/null and b/releases/0.37/img/react-native-android-studio-configure-sdk-windows.png differ diff --git a/releases/0.37/img/react-native-android-studio-configure-sdk.png b/releases/0.37/img/react-native-android-studio-configure-sdk.png new file mode 100644 index 00000000000..acfe1f30c8c Binary files /dev/null and b/releases/0.37/img/react-native-android-studio-configure-sdk.png differ diff --git a/releases/0.37/img/react-native-android-studio-custom-install-linux.png b/releases/0.37/img/react-native-android-studio-custom-install-linux.png new file mode 100644 index 00000000000..4410948cf84 Binary files /dev/null and b/releases/0.37/img/react-native-android-studio-custom-install-linux.png differ diff --git a/releases/0.37/img/react-native-android-studio-custom-install-windows.png b/releases/0.37/img/react-native-android-studio-custom-install-windows.png new file mode 100644 index 00000000000..7ed385a00e7 Binary files /dev/null and b/releases/0.37/img/react-native-android-studio-custom-install-windows.png differ diff --git a/releases/0.37/img/react-native-android-studio-custom-install.png b/releases/0.37/img/react-native-android-studio-custom-install.png new file mode 100644 index 00000000000..01ab7b2ac01 Binary files /dev/null and b/releases/0.37/img/react-native-android-studio-custom-install.png differ diff --git a/releases/0.37/img/react-native-android-studio-kvm-linux.png b/releases/0.37/img/react-native-android-studio-kvm-linux.png new file mode 100644 index 00000000000..dab081084e6 Binary files /dev/null and b/releases/0.37/img/react-native-android-studio-kvm-linux.png differ diff --git a/releases/0.37/img/react-native-android-studio-no-virtual-device-windows.png b/releases/0.37/img/react-native-android-studio-no-virtual-device-windows.png new file mode 100644 index 00000000000..933a583f03d Binary files /dev/null and b/releases/0.37/img/react-native-android-studio-no-virtual-device-windows.png differ diff --git a/releases/0.37/img/react-native-android-studio-verify-installs-windows.png b/releases/0.37/img/react-native-android-studio-verify-installs-windows.png new file mode 100644 index 00000000000..8f0cf1b2e20 Binary files /dev/null and b/releases/0.37/img/react-native-android-studio-verify-installs-windows.png differ diff --git a/releases/0.37/img/react-native-android-tools-environment-variable-windows.png b/releases/0.37/img/react-native-android-tools-environment-variable-windows.png new file mode 100644 index 00000000000..5ddeb614977 Binary files /dev/null and b/releases/0.37/img/react-native-android-tools-environment-variable-windows.png differ diff --git a/releases/0.37/img/react-native-congratulations.png b/releases/0.37/img/react-native-congratulations.png new file mode 100644 index 00000000000..92f520ec123 Binary files /dev/null and b/releases/0.37/img/react-native-congratulations.png differ diff --git a/releases/0.37/img/react-native-existing-app-integration-ios-before.png b/releases/0.37/img/react-native-existing-app-integration-ios-before.png new file mode 100644 index 00000000000..445dd790444 Binary files /dev/null and b/releases/0.37/img/react-native-existing-app-integration-ios-before.png differ diff --git a/releases/0.37/img/react-native-sdk-platforms.png b/releases/0.37/img/react-native-sdk-platforms.png new file mode 100644 index 00000000000..51d0317cfac Binary files /dev/null and b/releases/0.37/img/react-native-sdk-platforms.png differ diff --git a/releases/0.37/img/react-native-sorry-not-supported.png b/releases/0.37/img/react-native-sorry-not-supported.png new file mode 100644 index 00000000000..8848f4cdc6c Binary files /dev/null and b/releases/0.37/img/react-native-sorry-not-supported.png differ diff --git a/releases/0.37/img/react-native-tools-avd.png b/releases/0.37/img/react-native-tools-avd.png new file mode 100644 index 00000000000..66683ddd259 Binary files /dev/null and b/releases/0.37/img/react-native-tools-avd.png differ diff --git a/releases/0.37/img/search.png b/releases/0.37/img/search.png new file mode 100644 index 00000000000..222fb660dae Binary files /dev/null and b/releases/0.37/img/search.png differ diff --git a/releases/0.37/img/survey.png b/releases/0.37/img/survey.png new file mode 100644 index 00000000000..af025067da2 Binary files /dev/null and b/releases/0.37/img/survey.png differ diff --git a/releases/0.37/img/uiexplorer_main_android.png b/releases/0.37/img/uiexplorer_main_android.png new file mode 100644 index 00000000000..d510e7aded3 Binary files /dev/null and b/releases/0.37/img/uiexplorer_main_android.png differ diff --git a/releases/0.37/img/uiexplorer_main_ios.png b/releases/0.37/img/uiexplorer_main_ios.png new file mode 100644 index 00000000000..2274ef2dfb4 Binary files /dev/null and b/releases/0.37/img/uiexplorer_main_ios.png differ diff --git a/releases/0.37/index.html b/releases/0.37/index.html new file mode 100644 index 00000000000..f84f3ae0cf4 --- /dev/null +++ b/releases/0.37/index.html @@ -0,0 +1,73 @@ +React Native | A framework for building native apps using React
React Native
Learn once, write anywhere: Build mobile apps with React

Build Native Mobile Apps using JavaScript and React

React Native lets you build mobile apps using only JavaScript. It uses the same design as React, letting you compose a rich mobile UI from declarative components.

import React, { Component } from 'react'; +import { Text, View } from 'react-native'; + +class WhyReactNativeIsSoGreat extends Component { + render() { + return ( + <View> + <Text> + If you like React on the web, you'll like React Native. + </Text> + <Text> + You just use native components like 'View' and 'Text', + instead of web components like 'div' and 'span'. + </Text> + </View> + ); + } +}

A React Native App is a Real Mobile App

With React Native, you don't build a “mobile web app”, an “HTML5 app”, or a “hybrid app”. You build a real mobile app that's indistinguishable from an app built using Objective-C or Java. React Native uses the same fundamental UI building blocks as regular iOS and Android apps. You just put those building blocks together using JavaScript and React.

import React, { Component } from 'react'; +import { Image, ScrollView, Text } from 'react-native'; + +class AwkwardScrollingImageWithText extends Component { + render() { + return ( + <ScrollView> + <Image source={{uri: 'https://i.chzbgr.com/full/7345954048/h7E2C65F9/'}} /> + <Text> + On iOS, a React Native ScrollView uses a native UIScrollView. + On Android, it uses a native ScrollView. + + On iOS, a React Native Image uses a native UIImageView. + On Android, it uses a native ImageView. + + React Native wraps the fundamental native components, giving you + the performance of a native app, plus the clean design of React. + </Text> + </ScrollView> + ); + } +}

Don't Waste Time Recompiling

React Native lets you build your app faster. Instead of recompiling, you can reload your app instantly. With hot reloading, you can even run new code while retaining your application state. Give it a try - it's a magical experience.


Use Native Code When You Need To

React Native combines smoothly with components written in Objective-C, Java, or Swift. It's simple to drop down to native code if you need to optimize a few aspects of your application. It's also easy to build part of your app in React Native, and part of your app using native code directly - that's how the Facebook app works.

import React, { Component } from 'react'; +import { Text, View } from 'react-native'; +import { TheGreatestComponentInTheWorld } from './your-native-code'; + +class SomethingFast extends Component { + render() { + return ( + <View> + <TheGreatestComponentInTheWorld /> + <Text> + TheGreatestComponentInTheWorld could use native Objective-C, + Java, or Swift - the product development process is the same. + </Text> + </View> + ); + } +}

Who's using React Native?

Thousands of apps are using React Native, from established Fortune 500 companies to hot new startups. If you're curious to see what can be accomplished with React Native, check out these apps!

Facebook
Facebook Ads Manager
Facebook Groups
Instagram
Airbnb
Baidu(手机百度)
Discord
Gyroscope
li.st
QQ
Townske
Vogue

Some of these are hybrid native/React Native apps.

\ No newline at end of file diff --git a/releases/0.37/js/scripts.js b/releases/0.37/js/scripts.js new file mode 100644 index 00000000000..52858cf703a --- /dev/null +++ b/releases/0.37/js/scripts.js @@ -0,0 +1,91 @@ +(function(){ + // Not on browser + if (typeof document === 'undefined') { + return; + } + + document.addEventListener('DOMContentLoaded', init); + + function init() { + var mobile = isMobile(); + + if (mobile) { + document.querySelector('.nav-site-wrapper a[data-target]').addEventListener('click', toggleTarget); + } + + var webPlayerList = document.querySelectorAll('.web-player'); + + // Either show interactive or static code block, depending on desktop or mobile + for (var i = 0; i < webPlayerList.length; ++i) { + webPlayerList[i].classList.add(mobile ? 'mobile' : 'desktop'); + + if (!mobile) { + + // Determine location to look up required assets + var assetRoot = encodeURIComponent(document.location.origin + '/react-native'); + + // Set iframe src. Do this dynamically so the iframe never loads on mobile. + var iframe = webPlayerList[i].querySelector('iframe'); + iframe.src = iframe.getAttribute('data-src') + '&assetRoot=' + assetRoot; + } + } + + var backdrop = document.querySelector('.modal-backdrop'); + if (!backdrop) return; + + var modalButtonOpenList = document.querySelectorAll('.modal-button-open'); + var modalButtonClose = document.querySelector('.modal-button-close'); + + backdrop.addEventListener('click', hideModal); + modalButtonClose.addEventListener('click', hideModal); + + // Bind event to NodeList items + for (var i = 0; i < modalButtonOpenList.length; ++i) { + modalButtonOpenList[i].addEventListener('click', showModal); + } + } + + function showModal(e) { + var backdrop = document.querySelector('.modal-backdrop'); + if (!backdrop) return; + + var modal = document.querySelector('.modal'); + + backdrop.classList.add('modal-open'); + modal.classList.add('modal-open'); + } + + function hideModal(e) { + var backdrop = document.querySelector('.modal-backdrop'); + if (!backdrop) return; + + var modal = document.querySelector('.modal'); + + backdrop.classList.remove('modal-open'); + modal.classList.remove('modal-open'); + } + + var toggledTarget; + function toggleTarget(event) { + var target = document.body.querySelector(event.target.getAttribute('data-target')); + + if (target) { + event.preventDefault(); + + if (toggledTarget === target) { + toggledTarget.classList.toggle('in'); + } else { + toggledTarget && toggledTarget.classList.remove('in'); + target.classList.add('in'); + } + + toggledTarget = target; + } + } + + // Primitive mobile detection + function isMobile() { + return ( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ); + } + +}()); diff --git a/releases/0.37/movies.json b/releases/0.37/movies.json new file mode 100644 index 00000000000..fd48f44a9f5 --- /dev/null +++ b/releases/0.37/movies.json @@ -0,0 +1,11 @@ +{ + "title": "The Basics - Networking", + "description": "Your app fetched this from a remote endpoint!", + "movies": [ + { "title": "Star Wars", "releaseYear": "1977"}, + { "title": "Back to the Future", "releaseYear": "1985"}, + { "title": "The Matrix", "releaseYear": "1999"}, + { "title": "Inception", "releaseYear": "2010"}, + { "title": "Interstellar", "releaseYear": "2014"} + ] +} diff --git a/releases/0.37/versions.html b/releases/0.37/versions.html new file mode 100644 index 00000000000..62bf2fb85c2 --- /dev/null +++ b/releases/0.37/versions.html @@ -0,0 +1,19 @@ +React Native Versions

React Native Versions

React Native follows a 2-week release train. Every two weeks, a new branch created off master enters the Release Candidate phase, and the previous Release Candidate branch is released and considered stable.

Current Version (Stable)

0.36DocumentationRelease Notes

This is the version that is configured automatically when you run react-native init. We highly recommend using the current version of React Native when starting a new project.

If you have an existing project that uses React Native, read the release notes to learn about new features and fixes. You can follow our guide to upgrade your app to the latest version.

Pre-release Versions

masterDocumentation
0.37-RCDocumentationRelease Notes

For those who live on the bleeding edge. Only recommended if you're actively contributing code to React Native, or if you need to verify how your application behaves in an upcoming release.

Past Versions

0.35DocumentationRelease Notes
0.34DocumentationRelease Notes
0.33DocumentationRelease Notes
0.32DocumentationRelease Notes
0.31DocumentationRelease Notes
0.30DocumentationRelease Notes
0.29DocumentationRelease Notes
0.28DocumentationRelease Notes
0.27DocumentationRelease Notes
0.26DocumentationRelease Notes
0.25DocumentationRelease Notes
0.24DocumentationRelease Notes
0.23DocumentationRelease Notes
0.22DocumentationRelease Notes
0.21DocumentationRelease Notes
0.20DocumentationRelease Notes
0.19DocumentationRelease Notes
0.18DocumentationRelease Notes

You can find past versions of React Native on GitHub. The release notes can be useful if you would like to learn when a specific feature or fix was released.

You can also view the docs for a particular version of React Native by clicking on the Docs link next to the release in this page. You can come back to this page and switch the version of the docs you're reading at any time by clicking on the version number at the top of the page.

\ No newline at end of file diff --git a/versions.html b/versions.html index 630f2b6ae4d..62bf2fb85c2 100644 --- a/versions.html +++ b/versions.html @@ -1,4 +1,4 @@ -React Native Versions

React Native Versions

React Native follows a 2-week release train. Every two weeks, a new branch created off master enters the Release Candidate phase, and the previous Release Candidate branch is released and considered stable.

Current Version (Stable)

0.36DocumentationRelease Notes

This is the version that is configured automatically when you run react-native init. We highly recommend using the current version of React Native when starting a new project.

If you have an existing project that uses React Native, read the release notes to learn about new features and fixes. You can follow our guide to upgrade your app to the latest version.

Pre-release Versions

masterDocumentation

For those who live on the bleeding edge. Only recommended if you're actively contributing code to React Native, or if you need to verify how your application behaves in an upcoming release.

Past Versions

0.35DocumentationRelease Notes
0.34DocumentationRelease Notes
0.33DocumentationRelease Notes
0.32DocumentationRelease Notes
0.31DocumentationRelease Notes
0.30DocumentationRelease Notes
0.29DocumentationRelease Notes
0.28DocumentationRelease Notes
0.27DocumentationRelease Notes
0.26DocumentationRelease Notes
0.25DocumentationRelease Notes
0.24DocumentationRelease Notes
0.23DocumentationRelease Notes
0.22DocumentationRelease Notes
0.21DocumentationRelease Notes
0.20DocumentationRelease Notes
0.19DocumentationRelease Notes
0.18DocumentationRelease Notes

You can find past versions of React Native on GitHub. The release notes can be useful if you would like to learn when a specific feature or fix was released.

You can also view the docs for a particular version of React Native by clicking on the Docs link next to the release in this page. You can come back to this page and switch the version of the docs you're reading at any time by clicking on the version number at the top of the page.

React Native Versions

React Native follows a 2-week release train. Every two weeks, a new branch created off master enters the Release Candidate phase, and the previous Release Candidate branch is released and considered stable.

Current Version (Stable)

0.36DocumentationRelease Notes

This is the version that is configured automatically when you run react-native init. We highly recommend using the current version of React Native when starting a new project.

If you have an existing project that uses React Native, read the release notes to learn about new features and fixes. You can follow our guide to upgrade your app to the latest version.

Pre-release Versions

masterDocumentation
0.37-RCDocumentationRelease Notes

For those who live on the bleeding edge. Only recommended if you're actively contributing code to React Native, or if you need to verify how your application behaves in an upcoming release.

Past Versions

0.35DocumentationRelease Notes
0.34DocumentationRelease Notes
0.33DocumentationRelease Notes
0.32DocumentationRelease Notes
0.31DocumentationRelease Notes
0.30DocumentationRelease Notes
0.29DocumentationRelease Notes
0.28DocumentationRelease Notes
0.27DocumentationRelease Notes
0.26DocumentationRelease Notes
0.25DocumentationRelease Notes
0.24DocumentationRelease Notes
0.23DocumentationRelease Notes
0.22DocumentationRelease Notes
0.21DocumentationRelease Notes
0.20DocumentationRelease Notes
0.19DocumentationRelease Notes
0.18DocumentationRelease Notes

You can find past versions of React Native on GitHub. The release notes can be useful if you would like to learn when a specific feature or fix was released.

You can also view the docs for a particular version of React Native by clicking on the Docs link next to the release in this page. You can come back to this page and switch the version of the docs you're reading at any time by clicking on the version number at the top of the page.

\ No newline at end of file