├── .gitignore ├── CHANGELOG.md ├── LICENSE └── README.rst /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Version 1.2 4 | 5 | * Add function builders explanation 6 | * Add related contents about the update strategy 7 | 8 | ## Version 1.1 9 | 10 | * Fix typo and grammar issues 11 | 12 | ## Version 1.0 13 | 14 | * The first version -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Attribution-ShareAlike 4.0 International 2 | 3 | ======================================================================= 4 | 5 | Creative Commons Corporation ("Creative Commons") is not a law firm and 6 | does not provide legal services or legal advice. Distribution of 7 | Creative Commons public licenses does not create a lawyer-client or 8 | other relationship. Creative Commons makes its licenses and related 9 | information available on an "as-is" basis. Creative Commons gives no 10 | warranties regarding its licenses, any material licensed under their 11 | terms and conditions, or any related information. Creative Commons 12 | disclaims all liability for damages resulting from their use to the 13 | fullest extent possible. 14 | 15 | Using Creative Commons Public Licenses 16 | 17 | Creative Commons public licenses provide a standard set of terms and 18 | conditions that creators and other rights holders may use to share 19 | original works of authorship and other material subject to copyright 20 | and certain other rights specified in the public license below. The 21 | following considerations are for informational purposes only, are not 22 | exhaustive, and do not form part of our licenses. 23 | 24 | Considerations for licensors: Our public licenses are 25 | intended for use by those authorized to give the public 26 | permission to use material in ways otherwise restricted by 27 | copyright and certain other rights. Our licenses are 28 | irrevocable. Licensors should read and understand the terms 29 | and conditions of the license they choose before applying it. 30 | Licensors should also secure all rights necessary before 31 | applying our licenses so that the public can reuse the 32 | material as expected. Licensors should clearly mark any 33 | material not subject to the license. This includes other CC- 34 | licensed material, or material used under an exception or 35 | limitation to copyright. More considerations for licensors: 36 | wiki.creativecommons.org/Considerations_for_licensors 37 | 38 | Considerations for the public: By using one of our public 39 | licenses, a licensor grants the public permission to use the 40 | licensed material under specified terms and conditions. If 41 | the licensor's permission is not necessary for any reason--for 42 | example, because of any applicable exception or limitation to 43 | copyright--then that use is not regulated by the license. Our 44 | licenses grant only permissions under copyright and certain 45 | other rights that a licensor has authority to grant. Use of 46 | the licensed material may still be restricted for other 47 | reasons, including because others have copyright or other 48 | rights in the material. A licensor may make special requests, 49 | such as asking that all changes be marked or described. 50 | Although not required by our licenses, you are encouraged to 51 | respect those requests where reasonable. More considerations 52 | for the public: 53 | wiki.creativecommons.org/Considerations_for_licensees 54 | 55 | ======================================================================= 56 | 57 | Creative Commons Attribution-ShareAlike 4.0 International Public 58 | License 59 | 60 | By exercising the Licensed Rights (defined below), You accept and agree 61 | to be bound by the terms and conditions of this Creative Commons 62 | Attribution-ShareAlike 4.0 International Public License ("Public 63 | License"). To the extent this Public License may be interpreted as a 64 | contract, You are granted the Licensed Rights in consideration of Your 65 | acceptance of these terms and conditions, and the Licensor grants You 66 | such rights in consideration of benefits the Licensor receives from 67 | making the Licensed Material available under these terms and 68 | conditions. 69 | 70 | 71 | Section 1 -- Definitions. 72 | 73 | a. Adapted Material means material subject to Copyright and Similar 74 | Rights that is derived from or based upon the Licensed Material 75 | and in which the Licensed Material is translated, altered, 76 | arranged, transformed, or otherwise modified in a manner requiring 77 | permission under the Copyright and Similar Rights held by the 78 | Licensor. For purposes of this Public License, where the Licensed 79 | Material is a musical work, performance, or sound recording, 80 | Adapted Material is always produced where the Licensed Material is 81 | synched in timed relation with a moving image. 82 | 83 | b. Adapter's License means the license You apply to Your Copyright 84 | and Similar Rights in Your contributions to Adapted Material in 85 | accordance with the terms and conditions of this Public License. 86 | 87 | c. BY-SA Compatible License means a license listed at 88 | creativecommons.org/compatiblelicenses, approved by Creative 89 | Commons as essentially the equivalent of this Public License. 90 | 91 | d. Copyright and Similar Rights means copyright and/or similar rights 92 | closely related to copyright including, without limitation, 93 | performance, broadcast, sound recording, and Sui Generis Database 94 | Rights, without regard to how the rights are labeled or 95 | categorized. For purposes of this Public License, the rights 96 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 97 | Rights. 98 | 99 | e. Effective Technological Measures means those measures that, in the 100 | absence of proper authority, may not be circumvented under laws 101 | fulfilling obligations under Article 11 of the WIPO Copyright 102 | Treaty adopted on December 20, 1996, and/or similar international 103 | agreements. 104 | 105 | f. Exceptions and Limitations means fair use, fair dealing, and/or 106 | any other exception or limitation to Copyright and Similar Rights 107 | that applies to Your use of the Licensed Material. 108 | 109 | g. License Elements means the license attributes listed in the name 110 | of a Creative Commons Public License. The License Elements of this 111 | Public License are Attribution and ShareAlike. 112 | 113 | h. Licensed Material means the artistic or literary work, database, 114 | or other material to which the Licensor applied this Public 115 | License. 116 | 117 | i. Licensed Rights means the rights granted to You subject to the 118 | terms and conditions of this Public License, which are limited to 119 | all Copyright and Similar Rights that apply to Your use of the 120 | Licensed Material and that the Licensor has authority to license. 121 | 122 | j. Licensor means the individual(s) or entity(ies) granting rights 123 | under this Public License. 124 | 125 | k. Share means to provide material to the public by any means or 126 | process that requires permission under the Licensed Rights, such 127 | as reproduction, public display, public performance, distribution, 128 | dissemination, communication, or importation, and to make material 129 | available to the public including in ways that members of the 130 | public may access the material from a place and at a time 131 | individually chosen by them. 132 | 133 | l. Sui Generis Database Rights means rights other than copyright 134 | resulting from Directive 96/9/EC of the European Parliament and of 135 | the Council of 11 March 1996 on the legal protection of databases, 136 | as amended and/or succeeded, as well as other essentially 137 | equivalent rights anywhere in the world. 138 | 139 | m. You means the individual or entity exercising the Licensed Rights 140 | under this Public License. Your has a corresponding meaning. 141 | 142 | 143 | Section 2 -- Scope. 144 | 145 | a. License grant. 146 | 147 | 1. Subject to the terms and conditions of this Public License, 148 | the Licensor hereby grants You a worldwide, royalty-free, 149 | non-sublicensable, non-exclusive, irrevocable license to 150 | exercise the Licensed Rights in the Licensed Material to: 151 | 152 | a. reproduce and Share the Licensed Material, in whole or 153 | in part; and 154 | 155 | b. produce, reproduce, and Share Adapted Material. 156 | 157 | 2. Exceptions and Limitations. For the avoidance of doubt, where 158 | Exceptions and Limitations apply to Your use, this Public 159 | License does not apply, and You do not need to comply with 160 | its terms and conditions. 161 | 162 | 3. Term. The term of this Public License is specified in Section 163 | 6(a). 164 | 165 | 4. Media and formats; technical modifications allowed. The 166 | Licensor authorizes You to exercise the Licensed Rights in 167 | all media and formats whether now known or hereafter created, 168 | and to make technical modifications necessary to do so. The 169 | Licensor waives and/or agrees not to assert any right or 170 | authority to forbid You from making technical modifications 171 | necessary to exercise the Licensed Rights, including 172 | technical modifications necessary to circumvent Effective 173 | Technological Measures. For purposes of this Public License, 174 | simply making modifications authorized by this Section 2(a) 175 | (4) never produces Adapted Material. 176 | 177 | 5. Downstream recipients. 178 | 179 | a. Offer from the Licensor -- Licensed Material. Every 180 | recipient of the Licensed Material automatically 181 | receives an offer from the Licensor to exercise the 182 | Licensed Rights under the terms and conditions of this 183 | Public License. 184 | 185 | b. Additional offer from the Licensor -- Adapted Material. 186 | Every recipient of Adapted Material from You 187 | automatically receives an offer from the Licensor to 188 | exercise the Licensed Rights in the Adapted Material 189 | under the conditions of the Adapter's License You apply. 190 | 191 | c. No downstream restrictions. You may not offer or impose 192 | any additional or different terms or conditions on, or 193 | apply any Effective Technological Measures to, the 194 | Licensed Material if doing so restricts exercise of the 195 | Licensed Rights by any recipient of the Licensed 196 | Material. 197 | 198 | 6. No endorsement. Nothing in this Public License constitutes or 199 | may be construed as permission to assert or imply that You 200 | are, or that Your use of the Licensed Material is, connected 201 | with, or sponsored, endorsed, or granted official status by, 202 | the Licensor or others designated to receive attribution as 203 | provided in Section 3(a)(1)(A)(i). 204 | 205 | b. Other rights. 206 | 207 | 1. Moral rights, such as the right of integrity, are not 208 | licensed under this Public License, nor are publicity, 209 | privacy, and/or other similar personality rights; however, to 210 | the extent possible, the Licensor waives and/or agrees not to 211 | assert any such rights held by the Licensor to the limited 212 | extent necessary to allow You to exercise the Licensed 213 | Rights, but not otherwise. 214 | 215 | 2. Patent and trademark rights are not licensed under this 216 | Public License. 217 | 218 | 3. To the extent possible, the Licensor waives any right to 219 | collect royalties from You for the exercise of the Licensed 220 | Rights, whether directly or through a collecting society 221 | under any voluntary or waivable statutory or compulsory 222 | licensing scheme. In all other cases the Licensor expressly 223 | reserves any right to collect such royalties. 224 | 225 | 226 | Section 3 -- License Conditions. 227 | 228 | Your exercise of the Licensed Rights is expressly made subject to the 229 | following conditions. 230 | 231 | a. Attribution. 232 | 233 | 1. If You Share the Licensed Material (including in modified 234 | form), You must: 235 | 236 | a. retain the following if it is supplied by the Licensor 237 | with the Licensed Material: 238 | 239 | i. identification of the creator(s) of the Licensed 240 | Material and any others designated to receive 241 | attribution, in any reasonable manner requested by 242 | the Licensor (including by pseudonym if 243 | designated); 244 | 245 | ii. a copyright notice; 246 | 247 | iii. a notice that refers to this Public License; 248 | 249 | iv. a notice that refers to the disclaimer of 250 | warranties; 251 | 252 | v. a URI or hyperlink to the Licensed Material to the 253 | extent reasonably practicable; 254 | 255 | b. indicate if You modified the Licensed Material and 256 | retain an indication of any previous modifications; and 257 | 258 | c. indicate the Licensed Material is licensed under this 259 | Public License, and include the text of, or the URI or 260 | hyperlink to, this Public License. 261 | 262 | 2. You may satisfy the conditions in Section 3(a)(1) in any 263 | reasonable manner based on the medium, means, and context in 264 | which You Share the Licensed Material. For example, it may be 265 | reasonable to satisfy the conditions by providing a URI or 266 | hyperlink to a resource that includes the required 267 | information. 268 | 269 | 3. If requested by the Licensor, You must remove any of the 270 | information required by Section 3(a)(1)(A) to the extent 271 | reasonably practicable. 272 | 273 | b. ShareAlike. 274 | 275 | In addition to the conditions in Section 3(a), if You Share 276 | Adapted Material You produce, the following conditions also apply. 277 | 278 | 1. The Adapter's License You apply must be a Creative Commons 279 | license with the same License Elements, this version or 280 | later, or a BY-SA Compatible License. 281 | 282 | 2. You must include the text of, or the URI or hyperlink to, the 283 | Adapter's License You apply. You may satisfy this condition 284 | in any reasonable manner based on the medium, means, and 285 | context in which You Share Adapted Material. 286 | 287 | 3. You may not offer or impose any additional or different terms 288 | or conditions on, or apply any Effective Technological 289 | Measures to, Adapted Material that restrict exercise of the 290 | rights granted under the Adapter's License You apply. 291 | 292 | 293 | Section 4 -- Sui Generis Database Rights. 294 | 295 | Where the Licensed Rights include Sui Generis Database Rights that 296 | apply to Your use of the Licensed Material: 297 | 298 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 299 | to extract, reuse, reproduce, and Share all or a substantial 300 | portion of the contents of the database; 301 | 302 | b. if You include all or a substantial portion of the database 303 | contents in a database in which You have Sui Generis Database 304 | Rights, then the database in which You have Sui Generis Database 305 | Rights (but not its individual contents) is Adapted Material, 306 | 307 | including for purposes of Section 3(b); and 308 | c. You must comply with the conditions in Section 3(a) if You Share 309 | all or a substantial portion of the contents of the database. 310 | 311 | For the avoidance of doubt, this Section 4 supplements and does not 312 | replace Your obligations under this Public License where the Licensed 313 | Rights include other Copyright and Similar Rights. 314 | 315 | 316 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 317 | 318 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 319 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 320 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 321 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 322 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 323 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 324 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 325 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 326 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 327 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 328 | 329 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 330 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 331 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 332 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 333 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 334 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 335 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 336 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 337 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 338 | 339 | c. The disclaimer of warranties and limitation of liability provided 340 | above shall be interpreted in a manner that, to the extent 341 | possible, most closely approximates an absolute disclaimer and 342 | waiver of all liability. 343 | 344 | 345 | Section 6 -- Term and Termination. 346 | 347 | a. This Public License applies for the term of the Copyright and 348 | Similar Rights licensed here. However, if You fail to comply with 349 | this Public License, then Your rights under this Public License 350 | terminate automatically. 351 | 352 | b. Where Your right to use the Licensed Material has terminated under 353 | Section 6(a), it reinstates: 354 | 355 | 1. automatically as of the date the violation is cured, provided 356 | it is cured within 30 days of Your discovery of the 357 | violation; or 358 | 359 | 2. upon express reinstatement by the Licensor. 360 | 361 | For the avoidance of doubt, this Section 6(b) does not affect any 362 | right the Licensor may have to seek remedies for Your violations 363 | of this Public License. 364 | 365 | c. For the avoidance of doubt, the Licensor may also offer the 366 | Licensed Material under separate terms or conditions or stop 367 | distributing the Licensed Material at any time; however, doing so 368 | will not terminate this Public License. 369 | 370 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 371 | License. 372 | 373 | 374 | Section 7 -- Other Terms and Conditions. 375 | 376 | a. The Licensor shall not be bound by any additional or different 377 | terms or conditions communicated by You unless expressly agreed. 378 | 379 | b. Any arrangements, understandings, or agreements regarding the 380 | Licensed Material not stated herein are separate from and 381 | independent of the terms and conditions of this Public License. 382 | 383 | 384 | Section 8 -- Interpretation. 385 | 386 | a. For the avoidance of doubt, this Public License does not, and 387 | shall not be interpreted to, reduce, limit, restrict, or impose 388 | conditions on any use of the Licensed Material that could lawfully 389 | be made without permission under this Public License. 390 | 391 | b. To the extent possible, if any provision of this Public License is 392 | deemed unenforceable, it shall be automatically reformed to the 393 | minimum extent necessary to make it enforceable. If the provision 394 | cannot be reformed, it shall be severed from this Public License 395 | without affecting the enforceability of the remaining terms and 396 | conditions. 397 | 398 | c. No term or condition of this Public License will be waived and no 399 | failure to comply consented to unless expressly agreed to by the 400 | Licensor. 401 | 402 | d. Nothing in this Public License constitutes or may be interpreted 403 | as a limitation upon, or waiver of, any privileges and immunities 404 | that apply to the Licensor or You, including from the legal 405 | processes of any jurisdiction or authority. 406 | 407 | 408 | ======================================================================= 409 | 410 | Creative Commons is not a party to its public licenses. 411 | Notwithstanding, Creative Commons may elect to apply one of its public 412 | licenses to material it publishes and in those instances will be 413 | considered the “Licensor.” The text of the Creative Commons public 414 | licenses is dedicated to the public domain under the CC0 Public Domain 415 | Dedication. Except for the limited purpose of indicating that material 416 | is shared under a Creative Commons public license or as otherwise 417 | permitted by the Creative Commons policies published at 418 | creativecommons.org/policies, Creative Commons does not authorize the 419 | use of the trademark "Creative Commons" or any other trademark or logo 420 | of Creative Commons without its prior written consent including, 421 | without limitation, in connection with any unauthorized modifications 422 | to any of its public licenses or any other arrangements, 423 | understandings, or agreements concerning use of licensed material. For 424 | the avoidance of doubt, this paragraph does not form part of the public 425 | licenses. 426 | 427 | Creative Commons may be contacted at creativecommons.org. -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ============================ 2 | SwiftUI for React Developers 3 | ============================ 4 | This is a cheat sheet that helps you React developers to quickly start with SwiftUI. 5 | 6 | .. note:: I assume that you are familiar with React Hooks. For the transformation from **Class Components** to **Hooks**, I highly recommend you to visit `Thinking in React Hooks`_, which is a great visualized explanation. 7 | 8 | .. contents:: :local: 9 | 10 | Basics 11 | ====== 12 | 13 | Building the Contents 14 | --------------------- 15 | One of the core parts of these declarative UI frameworks is its DSL syntax, 16 | both of them do provide the special inline syntax for building the content. 17 | For React, that calls JSX and need to be transpiled by **Babel (with plugins)** 18 | or **tsc**. For SwiftUI, it's a built-in syntax in Swift 5.1 called 19 | **Function Builders**. 20 | 21 | In React: 22 | 23 | .. code-block:: javascript 24 | 25 | const Hello = () => { 26 | return ( 27 |
28 |

Hello

29 |

React is awesome!

30 |
31 | ); 32 | }; 33 | 34 | In SwiftUI: 35 | 36 | .. code-block:: swift 37 | 38 | struct Hello: View { 39 | var body: some View { 40 | VStack { 41 | Text("Hello") 42 | Text("SwiftUI is awesome!") 43 | } 44 | } 45 | } 46 | 47 | As you can see, Swift's syntax feels more natural and JSX seems to be more exotic. 48 | Actually, Web developers should be more familiar with JSX, after all, it's just 49 | like HTML. 50 | 51 | Props 52 | ----- 53 | Most of components render different contents depend on what input is given to it. 54 | That is what props comes to play. 55 | 56 | In React: 57 | 58 | .. code-block:: javascript 59 | 60 | const Hello = ({name}) => { 61 | return

Hello, {name}!

; 62 | }; 63 | 64 | In SwiftUI: 65 | 66 | .. code-block:: swift 67 | 68 | struct Hello: View { 69 | let name: String 70 | 71 | var body: some View { 72 | Text("Hello, \(name)!") 73 | } 74 | } 75 | 76 | Almost the same in semantic! 77 | 78 | Conditional & List 79 | ------------------ 80 | Structure of the contents can be dynamic, the most common patterns are conditional 81 | and list. 82 | 83 | In React: 84 | 85 | .. code-block:: javascript 86 | 87 | const UserList = ({ users }) => { 88 | if (!users.length) { 89 | return

No users

; 90 | } 91 | 92 | return ( 93 | 98 | ); 99 | } 100 | 101 | In SwiftUI: 102 | 103 | .. code-block:: swift 104 | 105 | struct UserList: View { 106 | let users: [User] 107 | 108 | var body: some View { 109 | Group { 110 | if users.isEmpty { 111 | Text("No users") 112 | } else { 113 | VStack { 114 | ForEach(users, id: \.id) { 115 | Text("\($0.username)") 116 | } 117 | } 118 | } 119 | } 120 | } 121 | } 122 | 123 | SwiftUI has built-in ``ForEach`` element, you don't need to manually map the data 124 | array to views, so you can have a much neater code. 125 | 126 | Events Handling 127 | --------------- 128 | In React: 129 | 130 | .. code-block:: javascript 131 | 132 | const Hello = () => { 133 | const clickHandler = useCallback(e => { 134 | console.log('Yay, the button is clicked!'); 135 | }, []); 136 | return ; 137 | }; 138 | 139 | In SwiftUI: 140 | 141 | .. code-block:: swift 142 | 143 | struct Hello: View { 144 | var body: some View { 145 | Button("Click Me") { 146 | print("Yay, the button is clicked!") 147 | } 148 | } 149 | } 150 | 151 | SwiftUI looks cleaner because there is no ``useCallback`` meme. In JavaScript, if 152 | you create a function inside another function (let's say ``foo``), the former 153 | always has a different reference every time ``foo`` is called. That means, the 154 | component receives the function as a **prop** will be rerendered every time. 155 | 156 | In consideration of performance, React provided ``useCallback``. It takes a value 157 | as **dependency**, and will return the same reference if the dependency is not 158 | changed. 159 | 160 | In SwiftUI, Apple have not provided such mechanism, and developers can just take 161 | no account of that. 162 | 163 | State 164 | ----- 165 | Sometimes, a component may retain some internal state even it's get updated by new 166 | props. Or it need to update itself without the props changed. State was born for 167 | this mission. 168 | 169 | The example combines all the things we've talked above. Let's create a simple 170 | counter. 171 | 172 | In React: 173 | 174 | .. code-block:: javascript 175 | 176 | const Counter = ({ initialValue }) => { 177 | const [counter, setCounter] = useState(initialValue); 178 | const increaseCounter = useCallback(() => { 179 | setCounter(counter + 1); 180 | }, [counter]); 181 | 182 | return ( 183 |
184 |

{counter}

185 | 186 |
187 | ); 188 | }; 189 | 190 | In SwiftUI: 191 | 192 | .. code-block:: swift 193 | 194 | struct Counter: View { 195 | let initialValue: Int 196 | 197 | @State 198 | var counter: Int 199 | 200 | init(initialValue: Int) { 201 | self.initialValue = initialValue 202 | _counter = State(initialValue: initialValue) 203 | } 204 | 205 | var body: some View { 206 | VStack { 207 | Text("\(counter)") 208 | Button("Increase") { 209 | self.counter += 1 210 | } 211 | } 212 | } 213 | } 214 | 215 | It seems to be a little complicated, let's decompose them into pieces. 216 | 217 | The counter has a internal state: ``counter``, and it's initial value is from the 218 | input props. In SwiftUI, a state is declared with ``@State`` property wrapper. 219 | I'll explain that later but now, you could just consider it as a special mark. 220 | 221 | The real ``counter`` value is wrapped in the ``_counter`` member variable (which 222 | has type of ``State``), and we can use the input prop ``initialValue`` to 223 | initialize it. 224 | 225 | We trigger an update by directly setting the ``counter`` value. This is not just 226 | an assignment, instead, this will cause some logic inside ``State`` to take effect 227 | and notify the SwiftUI framework to update our view. SwiftUI packed the ``xxx`` 228 | and ``setXXX`` functions into this little syntactic sugar to simplify our code. 229 | 230 | Effects 231 | ------- 232 | How can we perform some side-effects when the component is updated? In React, we 233 | have ``useEffect``: 234 | 235 | .. code-block:: javascript 236 | 237 | const Hello = ({ greeting, name }) => { 238 | useEffect(() => { 239 | console.log(`Hey, ${name}!`); 240 | }, [name]); 241 | 242 | useEffect(() => { 243 | console.log('Something changed!'); 244 | }); 245 | 246 | return

{greeting}, {name}!

; 247 | }; 248 | 249 | In SwiftUI: 250 | 251 | .. code-block:: swift 252 | 253 | func uniqueId() -> some Equatable { 254 | return UUID().uuidString // Maybe not so unique? 255 | } 256 | 257 | struct Hello: View { 258 | let greeting: String 259 | let name: String 260 | 261 | var body: some View { 262 | Text("\(greeting), \(name)!") 263 | .onChange(of: name) { name in 264 | print("Hey, \(name)!") 265 | } 266 | .onChange(of: uniqueId()) { _ in 267 | print("Something changed!") 268 | } 269 | } 270 | } 271 | 272 | In SwiftUI, we have neither hook functions nor lifecycle functions, but we have 273 | modifiers! Every view type has a lot of modifier functions attached to it. 274 | 275 | ``onChange`` behaves just like ``useEffect``, the ``action`` closure is called 276 | every time the ``value`` changes and the first time the receiver view renders. 277 | But we must pass a value, if you need perform something whenever something 278 | changed, you can use a trick: 279 | 280 | Create a function that returns an unique object every time it gets called. You can 281 | use **UUID**, global incrementing integer and even timestamps! 282 | 283 | Lifecycle Callbacks 284 | ------------------- 285 | In React: 286 | 287 | .. code-block:: javascript 288 | 289 | const Hello = () => { 290 | useEffect(() => { 291 | console.log('I\'m just mounted!'); 292 | return () => { 293 | console.log('I\'m just unmounted!'); 294 | }; 295 | }, []); 296 | 297 | return

Hello

; 298 | }; 299 | 300 | In SwiftUI: 301 | 302 | .. code-block:: swift 303 | 304 | struct Hello: View { 305 | var body: some View { 306 | Text("Hello") 307 | .onAppear { 308 | print("I'm just mounted!") 309 | } 310 | .onDisappear { 311 | print("I'm just unmounted!") 312 | } 313 | } 314 | } 315 | 316 | It's that easy. 317 | 318 | Refs 319 | ---- 320 | Components can have some internal state that will not trigger view update when it 321 | is changed. In React, we have **ref**: 322 | 323 | In React: 324 | 325 | .. code-block:: javascript 326 | 327 | const Hello = () => { 328 | const timerId = useRef(-1); 329 | useEffect(() => { 330 | timerId.current = setInterval(() => { 331 | console.log('Tick!'); 332 | }, 1000); 333 | return () => { 334 | clearInterval(timerId.current); 335 | }; 336 | }); 337 | 338 | return

Hello

; 339 | }; 340 | 341 | In SwiftUI: 342 | 343 | .. code-block:: swift 344 | 345 | struct Hello: View { 346 | private class Refs: ObservableObject { 347 | var timer: Timer? 348 | } 349 | 350 | @StateObject 351 | private var refs = Refs() 352 | 353 | var body: some View { 354 | Text("Hello") 355 | .onAppear { 356 | refs.timer = 357 | Timer.scheduledTimer(withTimeInterval: 1, 358 | repeats: true) { _ in 359 | print("Tick!") 360 | } 361 | } 362 | .onDisappear { 363 | refs.timer?.invalidate() 364 | } 365 | } 366 | } 367 | 368 | And we've got two approaches: 369 | 370 | .. code-block:: swift 371 | 372 | struct Hello: View { 373 | @State 374 | private var timer: Timer? = nil 375 | 376 | var body: some View { 377 | Text("Hello") 378 | .onAppear { 379 | self.timer = 380 | Timer.scheduledTimer(withTimeInterval: 1, 381 | repeats: true) { _ in 382 | print("Tick!") 383 | } 384 | } 385 | .onDisappear { 386 | self.timer?.invalidate() 387 | } 388 | } 389 | } 390 | 391 | You may wonder why setting the state will not lead to view updates. SwiftUI is 392 | pretty clever to handle the state, it uses a technique called 393 | **Dependency Tracking**. If you are familiar with **Vue.js** or **MobX**, you may 394 | understand it immediately. That's say, if we never **access** the state's value in 395 | the view's building process (which not includes ``onAppear`` calls), that state 396 | will be unbound and can be updated freely without causing view updates. 397 | 398 | DOM Refs 399 | -------- 400 | Accessing the native DOM object is an advanced but essential feature for Web 401 | frontend development. 402 | 403 | In React: 404 | 405 | .. code-block:: javascript 406 | 407 | const Hello = () => { 408 | const pEl = useRef(); 409 | useEffect(() => { 410 | pEl.current.innerHTML = 'Hello, world!'; 411 | }, []); 412 | 413 | return

; 414 | }; 415 | 416 | In SwiftUI, we apparently don't have DOM, but for native applications, **View** is 417 | a common concept. We can bridge native views to SwiftUI and gain control of them by 418 | the way. 419 | 420 | First, let's bridge an existed ``UIView`` to SwiftUI: 421 | 422 | .. code-block:: swift 423 | 424 | struct MapView: UIViewRepresentable { 425 | let mapType: MKMapType 426 | let ref: RefBox 427 | 428 | typealias UIViewType = MKMapView 429 | 430 | func makeUIView(context: Context) -> MKMapView { 431 | return MKMapView(frame: .zero) 432 | } 433 | 434 | func updateUIView(_ uiView: MKMapView, context: Context) { 435 | uiView.mapType = mapType 436 | ref.current = uiView 437 | } 438 | } 439 | 440 | Every time we modified the input props, the ``updateUIView`` gets called, we can 441 | update our ``UIView`` there. To export the ``UIView`` instance to the outer, we 442 | declare a ref prop, and set it's ``current`` property to the view instance 443 | whenever the ``updateUIView`` gets called. 444 | 445 | Now we can manipulate the native view in our SwiftUI views: 446 | 447 | .. code-block:: swift 448 | 449 | struct Hello: View { 450 | @State 451 | var mapType = MKMapType.standard 452 | 453 | @StateObject 454 | var mapViewRef = RefBox() 455 | 456 | var body: some View { 457 | VStack { 458 | MapView(mapType: mapType, ref: mapViewRef) 459 | Picker("Map Type", selection: $mapType) { 460 | Text("Standard").tag(MKMapType.standard) 461 | Text("Satellite").tag(MKMapType.satellite) 462 | Text("Hybrid").tag(MKMapType.hybrid) 463 | } 464 | .pickerStyle(SegmentedPickerStyle()) 465 | } 466 | .onAppear { 467 | if let mapView = self.mapViewRef.current { 468 | mapView.setRegion(.init(center: .init(latitude: 34, longitude: 108), 469 | span: MKCoordinateSpan(latitudeDelta: 50, 470 | longitudeDelta: 60)), 471 | animated: true) 472 | } 473 | } 474 | } 475 | } 476 | 477 | Note that, we'd better encapsulate all the manipulations of native views to a 478 | dedicated SwiftUI view. It's not a good practice to manipulate native objects 479 | everywhere, as well as in React. 480 | 481 | Context 482 | ------- 483 | Passing data between the components can be hard, especially when you travel 484 | through the hierachy. And **Context** to the rescue! 485 | 486 | Let's look at an example in React: 487 | 488 | .. code-block:: javascript 489 | 490 | const UserContext = createContext({}); 491 | 492 | const UserInfo = () => { 493 | const { username, logout } = useContext(UserContext); 494 | if (!username) { 495 | return

Welcome, please login.

; 496 | } 497 | return ( 498 |

499 | Hello, {username}. 500 | 501 |

502 | ); 503 | } 504 | 505 | const Panel = () => { 506 | return ( 507 |
508 | 509 | 510 |
511 | ); 512 | } 513 | 514 | const App = () => { 515 | const [username, setUsername] = useState('cyan'); 516 | const logout = useCallback(() => { 517 | setUsername(null); 518 | }, [setUsername]); 519 | return ( 520 | 521 | 522 | 523 | 524 | ); 525 | } 526 | 527 | Even if the ```` is at a very deep position, we can use context to grab 528 | the data we need through the tree. And also, contexts are often used by components 529 | to communicate with each other. 530 | 531 | In SwiftUI: 532 | 533 | .. code-block:: swift 534 | 535 | class UserContext: ObservableObject { 536 | @Published 537 | var username: String? 538 | 539 | init(username: String?) { 540 | self.username = username 541 | } 542 | 543 | func logout() { 544 | self.username = nil 545 | } 546 | } 547 | 548 | struct UserInfo: View { 549 | @EnvironmentObject 550 | var userContext: UserContext 551 | 552 | var body: some View { 553 | Group { 554 | if userContext.username == nil { 555 | Text("Welcome, please login.") 556 | } else { 557 | HStack { 558 | Text("Hello, \(userContext.username!).") 559 | Button("Logout") { 560 | self.userContext.logout() 561 | } 562 | } 563 | } 564 | } 565 | } 566 | } 567 | 568 | struct Panel: View { 569 | var body: some View { 570 | VStack { 571 | UserInfo() 572 | UserInfo() 573 | } 574 | } 575 | } 576 | 577 | struct App: View { 578 | @StateObject 579 | var userContext = UserContext(username: "cyan") 580 | 581 | var body: some View { 582 | VStack { 583 | Panel() 584 | Panel() 585 | } 586 | .environmentObject(userContext) 587 | } 588 | } 589 | 590 | Contexts are provided by ``environmentObject`` modifier and can be retrieved via 591 | ``@EnvironmentObject`` property wrapper. And in SwiftUI, context objects can use 592 | to update views. We don't need to wrap some functions that modifies the provider 593 | into the context objects. Context objects are ``ObservableObject``, so they can 594 | notify all the consumers automatically when they are changed. 595 | 596 | Another interesting fact is that the contexts are identified by the type of 597 | context objects, thus we don't need to maintain the context objects globally. 598 | 599 | Implementations Behind the Scene 600 | ================================ 601 | 602 | What are ``View`` objects? 603 | -------------------------- 604 | In SwiftUI, the ``View`` objects are different from the ``React.Component`` objects. 605 | Actually, there is no ``React.Component`` equivalent in SwiftUI. ``View`` objects 606 | are stateless themselves, they are just like ``Widget`` objects in Flutter, which 607 | are used to describe the configuration of views. 608 | 609 | That means, if you want attach some state to the view, you must mark it using 610 | ``@State``. Any other member variables are transient and live shorter than the view. 611 | After all, ``View`` objects are created and destroyed frequently during the building 612 | process, but meanwhile views may keep stable. 613 | 614 | How ``@State`` works? 615 | --------------------- 616 | To explain this question, you should know what is ``property wrapper`` before. 617 | This proposal describe that in detail: `[SE-0258] Property Wrappers`_. 618 | 619 | Before the ``View`` is mounted, SwiftUI will use type metadata to find out all the 620 | ``State`` fields (backends of the properties marked with ``@State``), and add them 621 | to a ``DynamicPropertyBuffer`` sequentially, we call this process as "registration". 622 | 623 | The buffer is aware of the view's lifecycle. When a new ``View`` object is created, 624 | SwiftUI enumerates the ``State`` fields, and get its corresponding previous value 625 | from the buffer. These fields are identified by their storage index in container 626 | struct, pretty like how **Hook** works in React. 627 | 628 | In this way, even though the ``View`` objects are recreated frequently, as long as 629 | the view is not unmounted, the state will be kept. 630 | 631 | How function builders works? 632 | ---------------------------- 633 | As we mention earlier, SwiftUI use **Function Builders** as DSL to let us build 634 | contents. There is also a draft proposal about it: `Function builders (draft proposal)`_. 635 | 636 | Let's first take a look at how JSX is transpiled to JavaScript. We have this: 637 | 638 | .. code-block:: javascript 639 | 640 | const UserInfo = ({ users }) => { 641 | if (!users.length) { 642 | return

No users

; 643 | } 644 | 645 | return ( 646 |
647 |

Great!

648 |

We have {users.length} users!

649 |
650 | ); 651 | } 652 | 653 | And this is the output from Babel with ``react`` preset: 654 | 655 | .. code-block:: javascript 656 | 657 | const UserInfo = ({ 658 | users 659 | }) => { 660 | if (!users.length) { 661 | return /*#__PURE__*/React.createElement("p", null, "No users"); 662 | } 663 | 664 | return /*#__PURE__*/React.createElement("div", null, 665 | /*#__PURE__*/React.createElement("p", null, "Great!"), 666 | /*#__PURE__*/React.createElement("p", null, "We have ", users.length, " users!") 667 | ); 668 | }; 669 | 670 | Most of the structure is identical, and the HTML tags are transformed to ``React.createElement`` 671 | calls. That makes sense, the function doesn't produce component instances, instead, 672 | it produces elements. Elements describe how to configure components or DOM elements. 673 | 674 | Now, let's back to SwiftUI. There is the same example: 675 | 676 | .. code-block:: swift 677 | 678 | struct UserInfo: View { 679 | let users: [User] 680 | 681 | var body: some View { 682 | Group { 683 | if users.isEmpty { 684 | Text("No users") 685 | } else { 686 | VStack { 687 | Text("Great!") 688 | Text("We have \(users.count) users!") 689 | } 690 | } 691 | } 692 | } 693 | } 694 | 695 | And this is the actual code represented by it: 696 | 697 | .. code-block:: swift 698 | 699 | struct UserInfo: View { 700 | let users: [User] 701 | 702 | var body: some View { 703 | let v: _ConditionalContent>> 704 | if users.isEmpty { 705 | v = ViewBuilder.buildEither(first: Text("No users")) 706 | } else { 707 | v = ViewBuilder.buildEither(second: VStack { 708 | return ViewBuilder.buildBlock( 709 | Text("Great!"), 710 | Text("We have \(users.count) users!") 711 | ) 712 | }) 713 | } 714 | return v 715 | } 716 | } 717 | 718 | Voila! All the dynamic structures are replaced by ``ViewBuilder`` method calls. In 719 | this way, we can use a complex type to represent the structure. Like ``if`` 720 | statement will be transformed to ``ViewBuilder.buildEither`` call, and its return 721 | value contains the information of both ``if`` block and ``else`` block. 722 | 723 | ``ViewBuilder.buildBlock`` is used to represent a child element that contains 724 | multiple views. 725 | 726 | With function builders, you can even create your own DSLs. And this year in WWDC20, 727 | Apple released more features based on function builders, like **WidgetKit** and 728 | SwiftUI **App Structure**. 729 | 730 | How SwiftUI determine when to update a view? 731 | -------------------------------------------- 732 | All views in SwiftUI are like **PureComponent** in React by default. That means, 733 | all the member variables (props) will be used to evaluate the equality, of course 734 | it's shallow comparison. 735 | 736 | What if you want to customize the update strategy? If you take a look at the 737 | declaration of ``View`` protocol, you will notice this subtle thing: 738 | 739 | .. code-block:: swift 740 | 741 | extension View where Self : Equatable { 742 | 743 | /// Prevents the view from updating its child view when its new value is the 744 | /// same as its old value. 745 | @inlinable public func equatable() -> EquatableView 746 | } 747 | 748 | SwiftUI provides an ``EquatableView`` to let you achieve that. All you need to do 749 | is make your view type conform ``Equatable`` and implement the ``==`` function. 750 | Then wrap it into ``EquatableView`` at the call-site. 751 | 752 | .. References: 753 | 754 | .. _`Thinking in React Hooks`: https://wattenberger.com/blog/react-hooks 755 | .. _`[SE-0258] Property Wrappers`: https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-wrappers.md 756 | .. _`Function builders (draft proposal)`: https://github.com/apple/swift-evolution/blob/9992cf3c11c2d5e0ea20bee98657d93902d5b174/proposals/XXXX-function-builders.md --------------------------------------------------------------------------------