├── .gitignore ├── LICENSE ├── README.md ├── codable ├── .gitignore ├── FirestoreCodableSamples.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── swiftpm │ │ │ └── Package.resolved │ └── xcshareddata │ │ └── xcschemes │ │ └── FirestoreCodableSamples.xcscheme ├── FirestoreCodableSamples │ ├── App │ │ └── FirestoreCodableSamplesApp.swift │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Info.plist │ ├── Models │ │ ├── Article.swift │ │ ├── Book.swift │ │ ├── BookWithCoverImages.swift │ │ ├── BookWithGenre.swift │ │ ├── BookWithTags.swift │ │ ├── ColorEntry.swift │ │ ├── Office.swift │ │ └── ProgrammingLanguage.swift │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ ├── Screens │ │ ├── CustomizeMappingScreen.swift │ │ ├── ManuallyMappingSimpleTypesScreen.swift │ │ ├── MappingArraysScreen.swift │ │ ├── MappingArraysWithNestedTypesScreen.swift │ │ ├── MappingColorsScreen.swift │ │ ├── MappingCustomTypesScreen.swift │ │ ├── MappingEnumsScreen.swift │ │ ├── MappingGeoPointsScreen.swift │ │ ├── MappingSimpleTypesScreen.swift │ │ └── MenuScreen.swift │ └── Utilities │ │ ├── Color+Codable.swift │ │ ├── Color+FontColor.swift │ │ └── SampleScreen.swift ├── data │ ├── auth_export │ │ ├── accounts.json │ │ └── config.json │ ├── firebase-export-metadata.json │ └── firestore_export │ │ ├── all_namespaces │ │ └── all_kinds │ │ │ ├── all_namespaces_all_kinds.export_metadata │ │ │ └── output-0 │ │ └── firestore_export.overall_export_metadata ├── firebase.json ├── firestore.indexes.json ├── firestore.rules └── start.sh └── images ├── header.png ├── logo.png ├── swift-logo-512.png └── swift-logo.png /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/xcode,swift,android,swiftpm,firebase,cocoapods,androidstudio,visualstudiocode,swiftpackagemanager 3 | # Edit at https://www.gitignore.io/?templates=xcode,swift,android,swiftpm,firebase,cocoapods,androidstudio,visualstudiocode,swiftpackagemanager 4 | 5 | ### Android ### 6 | # Built application files 7 | *.apk 8 | *.ap_ 9 | *.aab 10 | 11 | # Files for the ART/Dalvik VM 12 | *.dex 13 | 14 | # Java class files 15 | *.class 16 | 17 | # Generated files 18 | bin/ 19 | gen/ 20 | out/ 21 | release/ 22 | 23 | # Gradle files 24 | .gradle/ 25 | build/ 26 | 27 | # Local configuration file (sdk path, etc) 28 | local.properties 29 | 30 | # Proguard folder generated by Eclipse 31 | proguard/ 32 | 33 | # Log Files 34 | *.log 35 | 36 | # Android Studio Navigation editor temp files 37 | .navigation/ 38 | 39 | # Android Studio captures folder 40 | captures/ 41 | 42 | # IntelliJ 43 | *.iml 44 | .idea/workspace.xml 45 | .idea/tasks.xml 46 | .idea/gradle.xml 47 | .idea/assetWizardSettings.xml 48 | .idea/dictionaries 49 | .idea/libraries 50 | # Android Studio 3 in .gitignore file. 51 | .idea/caches 52 | .idea/modules.xml 53 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 54 | .idea/navEditor.xml 55 | 56 | # Keystore files 57 | # Uncomment the following lines if you do not want to check your keystore files in. 58 | #*.jks 59 | #*.keystore 60 | 61 | # External native build folder generated in Android Studio 2.2 and later 62 | .externalNativeBuild 63 | 64 | # Google Services (e.g. APIs or Firebase) 65 | # google-services.json 66 | 67 | # Freeline 68 | freeline.py 69 | freeline/ 70 | freeline_project_description.json 71 | 72 | # fastlane 73 | fastlane/report.xml 74 | fastlane/Preview.html 75 | fastlane/screenshots 76 | fastlane/test_output 77 | fastlane/readme.md 78 | 79 | # Version control 80 | vcs.xml 81 | 82 | # lint 83 | lint/intermediates/ 84 | lint/generated/ 85 | lint/outputs/ 86 | lint/tmp/ 87 | # lint/reports/ 88 | 89 | ### Android Patch ### 90 | gen-external-apklibs 91 | output.json 92 | 93 | # Replacement of .externalNativeBuild directories introduced 94 | # with Android Studio 3.5. 95 | .cxx/ 96 | 97 | ### CocoaPods ### 98 | ## CocoaPods GitIgnore Template 99 | 100 | # CocoaPods - Only use to conserve bandwidth / Save time on Pushing 101 | # - Also handy if you have a large number of dependant pods 102 | # - AS PER https://guides.cocoapods.org/using/using-cocoapods.html NEVER IGNORE THE LOCK FILE 103 | Pods/ 104 | 105 | ### Firebase ### 106 | .idea 107 | **/node_modules/* 108 | **/.firebaserc 109 | 110 | ### Firebase Patch ### 111 | .runtimeconfig.json 112 | .firebase/ 113 | 114 | ### Swift ### 115 | # Xcode 116 | # 117 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 118 | 119 | ## Build generated 120 | DerivedData/ 121 | 122 | ## Various settings 123 | *.pbxuser 124 | !default.pbxuser 125 | *.mode1v3 126 | !default.mode1v3 127 | *.mode2v3 128 | !default.mode2v3 129 | *.perspectivev3 130 | !default.perspectivev3 131 | xcuserdata/ 132 | 133 | ## Other 134 | *.moved-aside 135 | *.xccheckout 136 | *.xcscmblueprint 137 | 138 | ## Obj-C/Swift specific 139 | *.hmap 140 | *.ipa 141 | *.dSYM.zip 142 | *.dSYM 143 | 144 | ## Playgrounds 145 | timeline.xctimeline 146 | playground.xcworkspace 147 | 148 | # Swift Package Manager 149 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 150 | # Packages/ 151 | # Package.pins 152 | # Package.resolved 153 | .build/ 154 | # Add this line if you want to avoid checking in Xcode SPM integration. 155 | # .swiftpm/xcode 156 | 157 | # CocoaPods 158 | # We recommend against adding the Pods directory to your .gitignore. However 159 | # you should judge for yourself, the pros and cons are mentioned at: 160 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 161 | # Pods/ 162 | # Add this line if you want to avoid checking in source code from the Xcode workspace 163 | # *.xcworkspace 164 | 165 | # Carthage 166 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 167 | # Carthage/Checkouts 168 | 169 | Carthage/Build 170 | 171 | # Accio dependency management 172 | Dependencies/ 173 | .accio/ 174 | 175 | # fastlane 176 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 177 | # screenshots whenever they are needed. 178 | # For more information about the recommended setup visit: 179 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 180 | 181 | fastlane/screenshots/**/*.png 182 | 183 | # Code Injection 184 | # After new code Injection tools there's a generated folder /iOSInjectionProject 185 | # https://github.com/johnno1962/injectionforxcode 186 | 187 | iOSInjectionProject/ 188 | 189 | ### SwiftPackageManager ### 190 | Packages 191 | xcuserdata 192 | 193 | 194 | ### SwiftPM ### 195 | 196 | 197 | ### VisualStudioCode ### 198 | .vscode/* 199 | !.vscode/settings.json 200 | !.vscode/tasks.json 201 | !.vscode/launch.json 202 | !.vscode/extensions.json 203 | 204 | ### VisualStudioCode Patch ### 205 | # Ignore all local history of files 206 | .history 207 | 208 | ### Xcode ### 209 | # Xcode 210 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 211 | 212 | ## User settings 213 | 214 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 215 | 216 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 217 | 218 | ## Xcode Patch 219 | *.xcodeproj/* 220 | !*.xcodeproj/project.pbxproj 221 | !*.xcodeproj/xcshareddata/ 222 | !*.xcworkspace/contents.xcworkspacedata 223 | /*.gcno 224 | 225 | ### Xcode Patch ### 226 | **/xcshareddata/WorkspaceSettings.xcsettings 227 | 228 | ### AndroidStudio ### 229 | # Covers files to be ignored for android development using Android Studio. 230 | 231 | # Built application files 232 | 233 | # Files for the ART/Dalvik VM 234 | 235 | # Java class files 236 | 237 | # Generated files 238 | 239 | # Gradle files 240 | .gradle 241 | 242 | # Signing files 243 | .signing/ 244 | 245 | # Local configuration file (sdk path, etc) 246 | 247 | # Proguard folder generated by Eclipse 248 | 249 | # Log Files 250 | 251 | # Android Studio 252 | /*/build/ 253 | /*/local.properties 254 | /*/out 255 | /*/*/build 256 | /*/*/production 257 | *.ipr 258 | *~ 259 | *.swp 260 | 261 | # Android Patch 262 | 263 | # External native build folder generated in Android Studio 2.2 and later 264 | 265 | # NDK 266 | obj/ 267 | 268 | # IntelliJ IDEA 269 | *.iws 270 | /out/ 271 | 272 | # User-specific configurations 273 | .idea/caches/ 274 | .idea/libraries/ 275 | .idea/shelf/ 276 | .idea/.name 277 | .idea/compiler.xml 278 | .idea/copyright/profiles_settings.xml 279 | .idea/encodings.xml 280 | .idea/misc.xml 281 | .idea/scopes/scope_settings.xml 282 | .idea/vcs.xml 283 | .idea/jsLibraryMappings.xml 284 | .idea/datasources.xml 285 | .idea/dataSources.ids 286 | .idea/sqlDataSources.xml 287 | .idea/dynamic.xml 288 | .idea/uiDesigner.xml 289 | 290 | # OS-specific files 291 | .DS_Store 292 | .DS_Store? 293 | ._* 294 | .Spotlight-V100 295 | .Trashes 296 | ehthumbs.db 297 | Thumbs.db 298 | 299 | # Legacy Eclipse project files 300 | .classpath 301 | .project 302 | .cproject 303 | .settings/ 304 | 305 | # Mobile Tools for Java (J2ME) 306 | .mtj.tmp/ 307 | 308 | # Package Files # 309 | *.war 310 | *.ear 311 | 312 | # virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml) 313 | hs_err_pid* 314 | 315 | ## Plugin-specific files: 316 | 317 | # mpeltonen/sbt-idea plugin 318 | .idea_modules/ 319 | 320 | # JIRA plugin 321 | atlassian-ide-plugin.xml 322 | 323 | # Mongo Explorer plugin 324 | .idea/mongoSettings.xml 325 | 326 | # Crashlytics plugin (for Android Studio and IntelliJ) 327 | com_crashlytics_export_strings.xml 328 | crashlytics.properties 329 | crashlytics-build.properties 330 | fabric.properties 331 | 332 | ### AndroidStudio Patch ### 333 | 334 | !/gradle/wrapper/gradle-wrapper.jar 335 | 336 | # End of https://www.gitignore.io/api/xcode,swift,android,swiftpm,firebase,cocoapods,androidstudio,visualstudiocode,swiftpackagemanager 337 | GoogleService-Info.plist 338 | /archive 339 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [![Contributors][contributors-shield]][contributors-url] 3 | [![Forks][forks-shield]][forks-url] 4 | [![Stargazers][stars-shield]][stars-url] 5 | [![Issues][issues-shield]][issues-url] 6 | [![Discussions][discussions-shield]][discussions-url] 7 | [![Feature Requests][featurerequest-shield]][featurerequest-url] 8 | [![License][license-shield]][license-url] 9 | [![LinkedIn][linkedin-shield]][linkedin-url] 10 | 11 | 12 |
13 |

14 | 15 | Logo 16 | 17 | 18 |

Swift & Firestore - The Comprehensive Guide

19 | 20 |

21 | Code for my series about Firestore and Swift 22 |
23 | Read the article » 24 |
25 |
26 | Report Bug 27 | · 28 | Request Feature 29 |

30 |

31 | 32 | 33 | 34 |
35 | Table of Contents 36 |
    37 |
  1. 38 | About The Project 39 |
  2. 40 |
  3. 41 | Getting Started 42 | 47 |
  4. 48 |
  5. Contributing
  6. 49 |
  7. License
  8. 50 |
51 |
52 | 53 | 54 | ## About 55 | 56 | This project shows how to use Swift's Codable API with Firestore. 57 | 58 | 59 | 60 | ## Getting Started 61 | 62 | ### Prerequisites 63 | 64 | To run this demo, you will need: 65 | * Xcode 12.x 66 | * Firebase CLI 67 | 68 | ### Installation 69 | 70 | 1. Install Xcode (see https://developer.apple.com/xcode/) 71 | 2. Install the Firebase CLI (see https://firebase.google.com/docs/cli) 72 | 73 | ### Running 74 | 75 | You won't have to set up a Firebase project to run this sample, as it makes use of the local emulator suite. 76 | 77 | 1. First, launch the Firebase Emulator suite: 78 | ```bash 79 | $ ./start.sh 80 | ``` 81 | 1. Open the project in Xcode 82 | ```bash 83 | $ xed . 84 | ``` 85 | 1. Run the iOS project on a local Simulator (so it can connect to the Firebase Emulator) 86 | 87 | 88 | 89 | ## Contributing 90 | 91 | Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**. 92 | 93 | 1. Fork the Project 94 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) 95 | 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) 96 | 4. Push to the Branch (`git push origin feature/AmazingFeature`) 97 | 5. Open a Pull Request 98 | 99 | 100 | 101 | 102 | ## License 103 | 104 | Distributed under the Apache 2 License. See `LICENSE` for more information. 105 | 106 | 107 | 108 | [contributors-shield]: https://img.shields.io/github/contributors/peterfriese/Swift-Firestore-Guide.svg?style=flat-square 109 | [contributors-url]: https://github.com/peterfriese/Swift-Firestore-Guide/graphs/contributors 110 | [forks-shield]: https://img.shields.io/github/forks/peterfriese/Swift-Firestore-Guide.svg?style=flat-square 111 | [forks-url]: https://github.com/peterfriese/Swift-Firestore-Guide/network/members 112 | [stars-shield]: https://img.shields.io/github/stars/peterfriese/Swift-Firestore-Guide.svg?style=flat-square 113 | [stars-url]: https://github.com/peterfriese/Swift-Firestore-Guide/stargazers 114 | [issues-shield]: https://img.shields.io/github/issues/peterfriese/Swift-Firestore-Guide.svg?style=flat-square 115 | [issues-url]: https://github.com/peterfriese/Swift-Firestore-Guide/issues 116 | [license-shield]: https://img.shields.io/github/license/peterfriese/Swift-Firestore-Guide.svg?style=flat-square 117 | [license-url]: https://github.com/peterfriese/Swift-Firestore-Guide/blob/master/LICENSE.txt 118 | 119 | [linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=flat-square&logo=linkedin&colorB=555 120 | [linkedin-url]: https://www.linkedin.com/in/peterfriese 121 | [product-screenshot]: images/screenshot.png 122 | 123 | [swift-shield]: https://img.shields.io/badge/swift-5.3-FA7343?logo=swift&color=FA7343&style=flat-square 124 | [swift-url]: https://swift.org 125 | 126 | [xcode-shield]: https://img.shields.io/badge/xcode-12.5_beta-1575F9?logo=Xcode&style=flat-square 127 | [xcode-url]: https://developer.apple.com/xcode/ 128 | 129 | [featurerequest-url]: https://github.com/peterfriese/Swift-Firestore-Guide/issues/new?assignees=&labels=type%3A+feature+request&template=feature_request.md 130 | [featurerequest-shield]: https://img.shields.io/github/issues/peterfriese/Swift-Firestore-Guide/feature-request?logo=github&style=flat-square 131 | [discussions-url]: https://github.com/peterfriese/Swift-Firestore-Guide/discussions 132 | [discussions-shield]: https://img.shields.io/badge/discussions-brightgreen?logo=github&style=flat-square 133 | -------------------------------------------------------------------------------- /codable/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | firebase-debug.log* 8 | firebase-debug.*.log* 9 | 10 | # Firebase cache 11 | .firebase/ 12 | 13 | # Firebase config 14 | 15 | # Uncomment this if you'd like others to create their own Firebase project. 16 | # For a team working on the same Firebase project(s), it is recommended to leave 17 | # it commented so all members can deploy to the same project(s) in .firebaserc. 18 | # .firebaserc 19 | 20 | # Runtime data 21 | pids 22 | *.pid 23 | *.seed 24 | *.pid.lock 25 | 26 | # Directory for instrumented libs generated by jscoverage/JSCover 27 | lib-cov 28 | 29 | # Coverage directory used by tools like istanbul 30 | coverage 31 | 32 | # nyc test coverage 33 | .nyc_output 34 | 35 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 36 | .grunt 37 | 38 | # Bower dependency directory (https://bower.io/) 39 | bower_components 40 | 41 | # node-waf configuration 42 | .lock-wscript 43 | 44 | # Compiled binary addons (http://nodejs.org/api/addons.html) 45 | build/Release 46 | 47 | # Dependency directories 48 | node_modules/ 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Optional REPL history 57 | .node_repl_history 58 | 59 | # Output of 'npm pack' 60 | *.tgz 61 | 62 | # Yarn Integrity file 63 | .yarn-integrity 64 | 65 | # dotenv environment variables file 66 | .env 67 | -------------------------------------------------------------------------------- /codable/FirestoreCodableSamples.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 52; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 88014F1C28F40D1F0081CB49 /* FirebaseFirestoreSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 88014F1B28F40D1F0081CB49 /* FirebaseFirestoreSwift */; }; 11 | 88014F1E28F412560081CB49 /* Office.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88014F1D28F412560081CB49 /* Office.swift */; }; 12 | 88014F2028F412AF0081CB49 /* Article.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88014F1F28F412AF0081CB49 /* Article.swift */; }; 13 | 8805D39425D49A7300851C96 /* FirebaseFirestore in Frameworks */ = {isa = PBXBuildFile; productRef = 8805D39325D49A7300851C96 /* FirebaseFirestore */; }; 14 | 884040DB27E3827D000B515A /* FirebaseAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 884040DA27E3827D000B515A /* FirebaseAuth */; }; 15 | 8845BD7A25C46965002AEC45 /* FirestoreCodableSamplesApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8845BD7925C46965002AEC45 /* FirestoreCodableSamplesApp.swift */; }; 16 | 8845BD7E25C46967002AEC45 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8845BD7D25C46967002AEC45 /* Assets.xcassets */; }; 17 | 8845BD8125C46967002AEC45 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8845BD8025C46967002AEC45 /* Preview Assets.xcassets */; }; 18 | 8845C98526020142008F2FA2 /* MenuScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8845C98426020142008F2FA2 /* MenuScreen.swift */; }; 19 | 8845C98726020601008F2FA2 /* SampleScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8845C98626020601008F2FA2 /* SampleScreen.swift */; }; 20 | 8845C98A26020FA3008F2FA2 /* MappingSimpleTypesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8845C98926020FA3008F2FA2 /* MappingSimpleTypesScreen.swift */; }; 21 | 8845C98C260346A9008F2FA2 /* ManuallyMappingSimpleTypesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8845C98B260346A9008F2FA2 /* ManuallyMappingSimpleTypesScreen.swift */; }; 22 | 8845C98E260383C6008F2FA2 /* BookWithGenre.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8845C98D260383C6008F2FA2 /* BookWithGenre.swift */; }; 23 | 8845C99026038433008F2FA2 /* MappingArraysScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8845C98F26038433008F2FA2 /* MappingArraysScreen.swift */; }; 24 | 8845C9922603A15A008F2FA2 /* BookWithTags.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8845C9912603A15A008F2FA2 /* BookWithTags.swift */; }; 25 | 8845C9942603A286008F2FA2 /* MappingArraysWithNestedTypesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8845C9932603A286008F2FA2 /* MappingArraysWithNestedTypesScreen.swift */; }; 26 | 8845C9962603A74C008F2FA2 /* BookWithCoverImages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8845C9952603A74C008F2FA2 /* BookWithCoverImages.swift */; }; 27 | 8845C9982603C853008F2FA2 /* MappingCustomTypesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8845C9972603C853008F2FA2 /* MappingCustomTypesScreen.swift */; }; 28 | 8845C99A2603E733008F2FA2 /* Color+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8845C9992603E733008F2FA2 /* Color+Codable.swift */; }; 29 | 8845C99F2604C064008F2FA2 /* Color+FontColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8845C99E2604C064008F2FA2 /* Color+FontColor.swift */; }; 30 | 8845C9A12604C5AE008F2FA2 /* MappingColorsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8845C9A02604C5AD008F2FA2 /* MappingColorsScreen.swift */; }; 31 | 8845C9A32604ECA0008F2FA2 /* ProgrammingLanguage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8845C9A22604ECA0008F2FA2 /* ProgrammingLanguage.swift */; }; 32 | 8845C9A526050395008F2FA2 /* CustomizeMappingScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8845C9A426050395008F2FA2 /* CustomizeMappingScreen.swift */; }; 33 | 8845C9A7260503D7008F2FA2 /* ColorEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8845C9A6260503D7008F2FA2 /* ColorEntry.swift */; }; 34 | 885435F2260897CC006188D9 /* MappingGeoPointsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 885435F1260897CC006188D9 /* MappingGeoPointsScreen.swift */; }; 35 | 887A4A802608EA7E009ACBB1 /* MappingEnumsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 887A4A7F2608EA7E009ACBB1 /* MappingEnumsScreen.swift */; }; 36 | 887DD2B425C47A23003F7A99 /* Book.swift in Sources */ = {isa = PBXBuildFile; fileRef = 887DD2B325C47A23003F7A99 /* Book.swift */; }; 37 | 88A294A02804AC0900B8F266 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 88A2949F2804AC0900B8F266 /* GoogleService-Info.plist */; }; 38 | /* End PBXBuildFile section */ 39 | 40 | /* Begin PBXFileReference section */ 41 | 88014F1D28F412560081CB49 /* Office.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Office.swift; sourceTree = ""; }; 42 | 88014F1F28F412AF0081CB49 /* Article.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Article.swift; sourceTree = ""; }; 43 | 8845BD7625C46965002AEC45 /* FirestoreCodableSamples.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FirestoreCodableSamples.app; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | 8845BD7925C46965002AEC45 /* FirestoreCodableSamplesApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirestoreCodableSamplesApp.swift; sourceTree = ""; }; 45 | 8845BD7D25C46967002AEC45 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 46 | 8845BD8025C46967002AEC45 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 47 | 8845BD8225C46967002AEC45 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 48 | 8845C98426020142008F2FA2 /* MenuScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuScreen.swift; sourceTree = ""; }; 49 | 8845C98626020601008F2FA2 /* SampleScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleScreen.swift; sourceTree = ""; }; 50 | 8845C98926020FA3008F2FA2 /* MappingSimpleTypesScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MappingSimpleTypesScreen.swift; sourceTree = ""; }; 51 | 8845C98B260346A9008F2FA2 /* ManuallyMappingSimpleTypesScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManuallyMappingSimpleTypesScreen.swift; sourceTree = ""; }; 52 | 8845C98D260383C6008F2FA2 /* BookWithGenre.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookWithGenre.swift; sourceTree = ""; }; 53 | 8845C98F26038433008F2FA2 /* MappingArraysScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MappingArraysScreen.swift; sourceTree = ""; }; 54 | 8845C9912603A15A008F2FA2 /* BookWithTags.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookWithTags.swift; sourceTree = ""; }; 55 | 8845C9932603A286008F2FA2 /* MappingArraysWithNestedTypesScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MappingArraysWithNestedTypesScreen.swift; sourceTree = ""; }; 56 | 8845C9952603A74C008F2FA2 /* BookWithCoverImages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookWithCoverImages.swift; sourceTree = ""; }; 57 | 8845C9972603C853008F2FA2 /* MappingCustomTypesScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MappingCustomTypesScreen.swift; sourceTree = ""; }; 58 | 8845C9992603E733008F2FA2 /* Color+Codable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Codable.swift"; sourceTree = ""; }; 59 | 8845C99E2604C064008F2FA2 /* Color+FontColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+FontColor.swift"; sourceTree = ""; }; 60 | 8845C9A02604C5AD008F2FA2 /* MappingColorsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MappingColorsScreen.swift; sourceTree = ""; }; 61 | 8845C9A22604ECA0008F2FA2 /* ProgrammingLanguage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgrammingLanguage.swift; sourceTree = ""; }; 62 | 8845C9A426050395008F2FA2 /* CustomizeMappingScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomizeMappingScreen.swift; sourceTree = ""; }; 63 | 8845C9A6260503D7008F2FA2 /* ColorEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorEntry.swift; sourceTree = ""; }; 64 | 885435F1260897CC006188D9 /* MappingGeoPointsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MappingGeoPointsScreen.swift; sourceTree = ""; }; 65 | 887A4A7F2608EA7E009ACBB1 /* MappingEnumsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MappingEnumsScreen.swift; sourceTree = ""; }; 66 | 887DD2B325C47A23003F7A99 /* Book.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Book.swift; sourceTree = ""; }; 67 | 88A2949F2804AC0900B8F266 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 68 | /* End PBXFileReference section */ 69 | 70 | /* Begin PBXFrameworksBuildPhase section */ 71 | 8845BD7325C46965002AEC45 /* Frameworks */ = { 72 | isa = PBXFrameworksBuildPhase; 73 | buildActionMask = 2147483647; 74 | files = ( 75 | 8805D39425D49A7300851C96 /* FirebaseFirestore in Frameworks */, 76 | 884040DB27E3827D000B515A /* FirebaseAuth in Frameworks */, 77 | 88014F1C28F40D1F0081CB49 /* FirebaseFirestoreSwift in Frameworks */, 78 | ); 79 | runOnlyForDeploymentPostprocessing = 0; 80 | }; 81 | /* End PBXFrameworksBuildPhase section */ 82 | 83 | /* Begin PBXGroup section */ 84 | 8845BD6D25C46965002AEC45 = { 85 | isa = PBXGroup; 86 | children = ( 87 | 8845BD7825C46965002AEC45 /* FirestoreCodableSamples */, 88 | 8845BD7725C46965002AEC45 /* Products */, 89 | 887DD2AD25C479FD003F7A99 /* Frameworks */, 90 | ); 91 | sourceTree = ""; 92 | }; 93 | 8845BD7725C46965002AEC45 /* Products */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | 8845BD7625C46965002AEC45 /* FirestoreCodableSamples.app */, 97 | ); 98 | name = Products; 99 | sourceTree = ""; 100 | }; 101 | 8845BD7825C46965002AEC45 /* FirestoreCodableSamples */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | 8845C98826020F68008F2FA2 /* Utilities */, 105 | 8845C98226020033008F2FA2 /* App */, 106 | 887DD2B125C47A14003F7A99 /* Models */, 107 | 8845C98326020127008F2FA2 /* Screens */, 108 | 8845BD7D25C46967002AEC45 /* Assets.xcassets */, 109 | 8845BD8225C46967002AEC45 /* Info.plist */, 110 | 88A2949F2804AC0900B8F266 /* GoogleService-Info.plist */, 111 | 8845BD7F25C46967002AEC45 /* Preview Content */, 112 | ); 113 | path = FirestoreCodableSamples; 114 | sourceTree = ""; 115 | }; 116 | 8845BD7F25C46967002AEC45 /* Preview Content */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | 8845BD8025C46967002AEC45 /* Preview Assets.xcassets */, 120 | ); 121 | path = "Preview Content"; 122 | sourceTree = ""; 123 | }; 124 | 8845C98226020033008F2FA2 /* App */ = { 125 | isa = PBXGroup; 126 | children = ( 127 | 8845BD7925C46965002AEC45 /* FirestoreCodableSamplesApp.swift */, 128 | ); 129 | path = App; 130 | sourceTree = ""; 131 | }; 132 | 8845C98326020127008F2FA2 /* Screens */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | 8845C98426020142008F2FA2 /* MenuScreen.swift */, 136 | 8845C98B260346A9008F2FA2 /* ManuallyMappingSimpleTypesScreen.swift */, 137 | 8845C98926020FA3008F2FA2 /* MappingSimpleTypesScreen.swift */, 138 | 8845C9972603C853008F2FA2 /* MappingCustomTypesScreen.swift */, 139 | 8845C98F26038433008F2FA2 /* MappingArraysScreen.swift */, 140 | 8845C9932603A286008F2FA2 /* MappingArraysWithNestedTypesScreen.swift */, 141 | 8845C9A02604C5AD008F2FA2 /* MappingColorsScreen.swift */, 142 | 8845C9A426050395008F2FA2 /* CustomizeMappingScreen.swift */, 143 | 885435F1260897CC006188D9 /* MappingGeoPointsScreen.swift */, 144 | 887A4A7F2608EA7E009ACBB1 /* MappingEnumsScreen.swift */, 145 | ); 146 | path = Screens; 147 | sourceTree = ""; 148 | }; 149 | 8845C98826020F68008F2FA2 /* Utilities */ = { 150 | isa = PBXGroup; 151 | children = ( 152 | 8845C98626020601008F2FA2 /* SampleScreen.swift */, 153 | 8845C9992603E733008F2FA2 /* Color+Codable.swift */, 154 | 8845C99E2604C064008F2FA2 /* Color+FontColor.swift */, 155 | ); 156 | path = Utilities; 157 | sourceTree = ""; 158 | }; 159 | 887DD2AD25C479FD003F7A99 /* Frameworks */ = { 160 | isa = PBXGroup; 161 | children = ( 162 | ); 163 | name = Frameworks; 164 | sourceTree = ""; 165 | }; 166 | 887DD2B125C47A14003F7A99 /* Models */ = { 167 | isa = PBXGroup; 168 | children = ( 169 | 887DD2B325C47A23003F7A99 /* Book.swift */, 170 | 8845C98D260383C6008F2FA2 /* BookWithGenre.swift */, 171 | 8845C9912603A15A008F2FA2 /* BookWithTags.swift */, 172 | 8845C9952603A74C008F2FA2 /* BookWithCoverImages.swift */, 173 | 8845C9A22604ECA0008F2FA2 /* ProgrammingLanguage.swift */, 174 | 8845C9A6260503D7008F2FA2 /* ColorEntry.swift */, 175 | 88014F1D28F412560081CB49 /* Office.swift */, 176 | 88014F1F28F412AF0081CB49 /* Article.swift */, 177 | ); 178 | path = Models; 179 | sourceTree = ""; 180 | }; 181 | /* End PBXGroup section */ 182 | 183 | /* Begin PBXNativeTarget section */ 184 | 8845BD7525C46965002AEC45 /* FirestoreCodableSamples */ = { 185 | isa = PBXNativeTarget; 186 | buildConfigurationList = 8845BD8525C46967002AEC45 /* Build configuration list for PBXNativeTarget "FirestoreCodableSamples" */; 187 | buildPhases = ( 188 | 8845BD7225C46965002AEC45 /* Sources */, 189 | 8845BD7325C46965002AEC45 /* Frameworks */, 190 | 8845BD7425C46965002AEC45 /* Resources */, 191 | ); 192 | buildRules = ( 193 | ); 194 | dependencies = ( 195 | ); 196 | name = FirestoreCodableSamples; 197 | packageProductDependencies = ( 198 | 8805D39325D49A7300851C96 /* FirebaseFirestore */, 199 | 884040DA27E3827D000B515A /* FirebaseAuth */, 200 | 88014F1B28F40D1F0081CB49 /* FirebaseFirestoreSwift */, 201 | ); 202 | productName = FirestoreCodableSamples; 203 | productReference = 8845BD7625C46965002AEC45 /* FirestoreCodableSamples.app */; 204 | productType = "com.apple.product-type.application"; 205 | }; 206 | /* End PBXNativeTarget section */ 207 | 208 | /* Begin PBXProject section */ 209 | 8845BD6E25C46965002AEC45 /* Project object */ = { 210 | isa = PBXProject; 211 | attributes = { 212 | LastSwiftUpdateCheck = 1240; 213 | LastUpgradeCheck = 1240; 214 | TargetAttributes = { 215 | 8845BD7525C46965002AEC45 = { 216 | CreatedOnToolsVersion = 12.4; 217 | }; 218 | }; 219 | }; 220 | buildConfigurationList = 8845BD7125C46965002AEC45 /* Build configuration list for PBXProject "FirestoreCodableSamples" */; 221 | compatibilityVersion = "Xcode 9.3"; 222 | developmentRegion = en; 223 | hasScannedForEncodings = 0; 224 | knownRegions = ( 225 | en, 226 | Base, 227 | ); 228 | mainGroup = 8845BD6D25C46965002AEC45; 229 | packageReferences = ( 230 | 8805D39225D49A7300851C96 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, 231 | ); 232 | productRefGroup = 8845BD7725C46965002AEC45 /* Products */; 233 | projectDirPath = ""; 234 | projectRoot = ""; 235 | targets = ( 236 | 8845BD7525C46965002AEC45 /* FirestoreCodableSamples */, 237 | ); 238 | }; 239 | /* End PBXProject section */ 240 | 241 | /* Begin PBXResourcesBuildPhase section */ 242 | 8845BD7425C46965002AEC45 /* Resources */ = { 243 | isa = PBXResourcesBuildPhase; 244 | buildActionMask = 2147483647; 245 | files = ( 246 | 88A294A02804AC0900B8F266 /* GoogleService-Info.plist in Resources */, 247 | 8845BD8125C46967002AEC45 /* Preview Assets.xcassets in Resources */, 248 | 8845BD7E25C46967002AEC45 /* Assets.xcassets in Resources */, 249 | ); 250 | runOnlyForDeploymentPostprocessing = 0; 251 | }; 252 | /* End PBXResourcesBuildPhase section */ 253 | 254 | /* Begin PBXSourcesBuildPhase section */ 255 | 8845BD7225C46965002AEC45 /* Sources */ = { 256 | isa = PBXSourcesBuildPhase; 257 | buildActionMask = 2147483647; 258 | files = ( 259 | 88014F2028F412AF0081CB49 /* Article.swift in Sources */, 260 | 8845C98E260383C6008F2FA2 /* BookWithGenre.swift in Sources */, 261 | 8845C9922603A15A008F2FA2 /* BookWithTags.swift in Sources */, 262 | 8845C99F2604C064008F2FA2 /* Color+FontColor.swift in Sources */, 263 | 8845C98526020142008F2FA2 /* MenuScreen.swift in Sources */, 264 | 8845C9A7260503D7008F2FA2 /* ColorEntry.swift in Sources */, 265 | 8845C9962603A74C008F2FA2 /* BookWithCoverImages.swift in Sources */, 266 | 8845C99026038433008F2FA2 /* MappingArraysScreen.swift in Sources */, 267 | 8845C98726020601008F2FA2 /* SampleScreen.swift in Sources */, 268 | 8845C9942603A286008F2FA2 /* MappingArraysWithNestedTypesScreen.swift in Sources */, 269 | 885435F2260897CC006188D9 /* MappingGeoPointsScreen.swift in Sources */, 270 | 887DD2B425C47A23003F7A99 /* Book.swift in Sources */, 271 | 8845C9A12604C5AE008F2FA2 /* MappingColorsScreen.swift in Sources */, 272 | 8845C98C260346A9008F2FA2 /* ManuallyMappingSimpleTypesScreen.swift in Sources */, 273 | 8845C9A32604ECA0008F2FA2 /* ProgrammingLanguage.swift in Sources */, 274 | 8845C9982603C853008F2FA2 /* MappingCustomTypesScreen.swift in Sources */, 275 | 88014F1E28F412560081CB49 /* Office.swift in Sources */, 276 | 8845C98A26020FA3008F2FA2 /* MappingSimpleTypesScreen.swift in Sources */, 277 | 8845C99A2603E733008F2FA2 /* Color+Codable.swift in Sources */, 278 | 8845BD7A25C46965002AEC45 /* FirestoreCodableSamplesApp.swift in Sources */, 279 | 8845C9A526050395008F2FA2 /* CustomizeMappingScreen.swift in Sources */, 280 | 887A4A802608EA7E009ACBB1 /* MappingEnumsScreen.swift in Sources */, 281 | ); 282 | runOnlyForDeploymentPostprocessing = 0; 283 | }; 284 | /* End PBXSourcesBuildPhase section */ 285 | 286 | /* Begin XCBuildConfiguration section */ 287 | 8845BD8325C46967002AEC45 /* Debug */ = { 288 | isa = XCBuildConfiguration; 289 | buildSettings = { 290 | ALWAYS_SEARCH_USER_PATHS = NO; 291 | CLANG_ANALYZER_NONNULL = YES; 292 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 293 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 294 | CLANG_CXX_LIBRARY = "libc++"; 295 | CLANG_ENABLE_MODULES = YES; 296 | CLANG_ENABLE_OBJC_ARC = YES; 297 | CLANG_ENABLE_OBJC_WEAK = YES; 298 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 299 | CLANG_WARN_BOOL_CONVERSION = YES; 300 | CLANG_WARN_COMMA = YES; 301 | CLANG_WARN_CONSTANT_CONVERSION = YES; 302 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 303 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 304 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 305 | CLANG_WARN_EMPTY_BODY = YES; 306 | CLANG_WARN_ENUM_CONVERSION = YES; 307 | CLANG_WARN_INFINITE_RECURSION = YES; 308 | CLANG_WARN_INT_CONVERSION = YES; 309 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 310 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 311 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 312 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 313 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 314 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 315 | CLANG_WARN_STRICT_PROTOTYPES = YES; 316 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 317 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 318 | CLANG_WARN_UNREACHABLE_CODE = YES; 319 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 320 | COPY_PHASE_STRIP = NO; 321 | DEBUG_INFORMATION_FORMAT = dwarf; 322 | ENABLE_STRICT_OBJC_MSGSEND = YES; 323 | ENABLE_TESTABILITY = YES; 324 | GCC_C_LANGUAGE_STANDARD = gnu11; 325 | GCC_DYNAMIC_NO_PIC = NO; 326 | GCC_NO_COMMON_BLOCKS = YES; 327 | GCC_OPTIMIZATION_LEVEL = 0; 328 | GCC_PREPROCESSOR_DEFINITIONS = ( 329 | "DEBUG=1", 330 | "$(inherited)", 331 | ); 332 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 333 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 334 | GCC_WARN_UNDECLARED_SELECTOR = YES; 335 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 336 | GCC_WARN_UNUSED_FUNCTION = YES; 337 | GCC_WARN_UNUSED_VARIABLE = YES; 338 | IPHONEOS_DEPLOYMENT_TARGET = 16.0; 339 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 340 | MTL_FAST_MATH = YES; 341 | ONLY_ACTIVE_ARCH = YES; 342 | SDKROOT = iphoneos; 343 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 344 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 345 | }; 346 | name = Debug; 347 | }; 348 | 8845BD8425C46967002AEC45 /* Release */ = { 349 | isa = XCBuildConfiguration; 350 | buildSettings = { 351 | ALWAYS_SEARCH_USER_PATHS = NO; 352 | CLANG_ANALYZER_NONNULL = YES; 353 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 354 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 355 | CLANG_CXX_LIBRARY = "libc++"; 356 | CLANG_ENABLE_MODULES = YES; 357 | CLANG_ENABLE_OBJC_ARC = YES; 358 | CLANG_ENABLE_OBJC_WEAK = YES; 359 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 360 | CLANG_WARN_BOOL_CONVERSION = YES; 361 | CLANG_WARN_COMMA = YES; 362 | CLANG_WARN_CONSTANT_CONVERSION = YES; 363 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 364 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 365 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 366 | CLANG_WARN_EMPTY_BODY = YES; 367 | CLANG_WARN_ENUM_CONVERSION = YES; 368 | CLANG_WARN_INFINITE_RECURSION = YES; 369 | CLANG_WARN_INT_CONVERSION = YES; 370 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 371 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 372 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 373 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 374 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 375 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 376 | CLANG_WARN_STRICT_PROTOTYPES = YES; 377 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 378 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 379 | CLANG_WARN_UNREACHABLE_CODE = YES; 380 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 381 | COPY_PHASE_STRIP = NO; 382 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 383 | ENABLE_NS_ASSERTIONS = NO; 384 | ENABLE_STRICT_OBJC_MSGSEND = YES; 385 | GCC_C_LANGUAGE_STANDARD = gnu11; 386 | GCC_NO_COMMON_BLOCKS = YES; 387 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 388 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 389 | GCC_WARN_UNDECLARED_SELECTOR = YES; 390 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 391 | GCC_WARN_UNUSED_FUNCTION = YES; 392 | GCC_WARN_UNUSED_VARIABLE = YES; 393 | IPHONEOS_DEPLOYMENT_TARGET = 16.0; 394 | MTL_ENABLE_DEBUG_INFO = NO; 395 | MTL_FAST_MATH = YES; 396 | SDKROOT = iphoneos; 397 | SWIFT_COMPILATION_MODE = wholemodule; 398 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 399 | VALIDATE_PRODUCT = YES; 400 | }; 401 | name = Release; 402 | }; 403 | 8845BD8625C46967002AEC45 /* Debug */ = { 404 | isa = XCBuildConfiguration; 405 | buildSettings = { 406 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 407 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 408 | CODE_SIGN_STYLE = Automatic; 409 | DEVELOPMENT_ASSET_PATHS = "\"FirestoreCodableSamples/Preview Content\""; 410 | DEVELOPMENT_TEAM = ""; 411 | ENABLE_PREVIEWS = YES; 412 | INFOPLIST_FILE = FirestoreCodableSamples/Info.plist; 413 | IPHONEOS_DEPLOYMENT_TARGET = 16.0; 414 | LD_RUNPATH_SEARCH_PATHS = ( 415 | "$(inherited)", 416 | "@executable_path/Frameworks", 417 | ); 418 | OTHER_LDFLAGS = ( 419 | "-Xlinker", 420 | "-interposable", 421 | ); 422 | PRODUCT_BUNDLE_IDENTIFIER = dev.peterfriese.FirestoreCodableSamples; 423 | PRODUCT_NAME = "$(TARGET_NAME)"; 424 | SWIFT_VERSION = 5.0; 425 | TARGETED_DEVICE_FAMILY = "1,2"; 426 | }; 427 | name = Debug; 428 | }; 429 | 8845BD8725C46967002AEC45 /* Release */ = { 430 | isa = XCBuildConfiguration; 431 | buildSettings = { 432 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 433 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 434 | CODE_SIGN_STYLE = Automatic; 435 | DEVELOPMENT_ASSET_PATHS = "\"FirestoreCodableSamples/Preview Content\""; 436 | DEVELOPMENT_TEAM = ""; 437 | ENABLE_PREVIEWS = YES; 438 | INFOPLIST_FILE = FirestoreCodableSamples/Info.plist; 439 | IPHONEOS_DEPLOYMENT_TARGET = 16.0; 440 | LD_RUNPATH_SEARCH_PATHS = ( 441 | "$(inherited)", 442 | "@executable_path/Frameworks", 443 | ); 444 | PRODUCT_BUNDLE_IDENTIFIER = dev.peterfriese.FirestoreCodableSamples; 445 | PRODUCT_NAME = "$(TARGET_NAME)"; 446 | SWIFT_VERSION = 5.0; 447 | TARGETED_DEVICE_FAMILY = "1,2"; 448 | }; 449 | name = Release; 450 | }; 451 | /* End XCBuildConfiguration section */ 452 | 453 | /* Begin XCConfigurationList section */ 454 | 8845BD7125C46965002AEC45 /* Build configuration list for PBXProject "FirestoreCodableSamples" */ = { 455 | isa = XCConfigurationList; 456 | buildConfigurations = ( 457 | 8845BD8325C46967002AEC45 /* Debug */, 458 | 8845BD8425C46967002AEC45 /* Release */, 459 | ); 460 | defaultConfigurationIsVisible = 0; 461 | defaultConfigurationName = Release; 462 | }; 463 | 8845BD8525C46967002AEC45 /* Build configuration list for PBXNativeTarget "FirestoreCodableSamples" */ = { 464 | isa = XCConfigurationList; 465 | buildConfigurations = ( 466 | 8845BD8625C46967002AEC45 /* Debug */, 467 | 8845BD8725C46967002AEC45 /* Release */, 468 | ); 469 | defaultConfigurationIsVisible = 0; 470 | defaultConfigurationName = Release; 471 | }; 472 | /* End XCConfigurationList section */ 473 | 474 | /* Begin XCRemoteSwiftPackageReference section */ 475 | 8805D39225D49A7300851C96 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = { 476 | isa = XCRemoteSwiftPackageReference; 477 | repositoryURL = "https://github.com/firebase/firebase-ios-sdk"; 478 | requirement = { 479 | kind = upToNextMajorVersion; 480 | minimumVersion = 9.0.0; 481 | }; 482 | }; 483 | /* End XCRemoteSwiftPackageReference section */ 484 | 485 | /* Begin XCSwiftPackageProductDependency section */ 486 | 88014F1B28F40D1F0081CB49 /* FirebaseFirestoreSwift */ = { 487 | isa = XCSwiftPackageProductDependency; 488 | package = 8805D39225D49A7300851C96 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; 489 | productName = FirebaseFirestoreSwift; 490 | }; 491 | 8805D39325D49A7300851C96 /* FirebaseFirestore */ = { 492 | isa = XCSwiftPackageProductDependency; 493 | package = 8805D39225D49A7300851C96 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; 494 | productName = FirebaseFirestore; 495 | }; 496 | 884040DA27E3827D000B515A /* FirebaseAuth */ = { 497 | isa = XCSwiftPackageProductDependency; 498 | package = 8805D39225D49A7300851C96 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; 499 | productName = FirebaseAuth; 500 | }; 501 | /* End XCSwiftPackageProductDependency section */ 502 | }; 503 | rootObject = 8845BD6E25C46965002AEC45 /* Project object */; 504 | } 505 | -------------------------------------------------------------------------------- /codable/FirestoreCodableSamples.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /codable/FirestoreCodableSamples.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /codable/FirestoreCodableSamples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "abseil-cpp-swiftpm", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/firebase/abseil-cpp-SwiftPM.git", 7 | "state" : { 8 | "revision" : "583de9bd60f66b40e78d08599cc92036c2e7e4e1", 9 | "version" : "0.20220203.2" 10 | } 11 | }, 12 | { 13 | "identity" : "boringssl-swiftpm", 14 | "kind" : "remoteSourceControl", 15 | "location" : "https://github.com/firebase/boringssl-SwiftPM.git", 16 | "state" : { 17 | "revision" : "dd3eda2b05a3f459fc3073695ad1b28659066eab", 18 | "version" : "0.9.1" 19 | } 20 | }, 21 | { 22 | "identity" : "firebase-ios-sdk", 23 | "kind" : "remoteSourceControl", 24 | "location" : "https://github.com/firebase/firebase-ios-sdk", 25 | "state" : { 26 | "revision" : "7e80c25b51c2ffa238879b07fbfc5baa54bb3050", 27 | "version" : "9.6.0" 28 | } 29 | }, 30 | { 31 | "identity" : "googleappmeasurement", 32 | "kind" : "remoteSourceControl", 33 | "location" : "https://github.com/google/GoogleAppMeasurement.git", 34 | "state" : { 35 | "revision" : "c1cfde8067668027b23a42c29d11c246152fe046", 36 | "version" : "9.6.0" 37 | } 38 | }, 39 | { 40 | "identity" : "googledatatransport", 41 | "kind" : "remoteSourceControl", 42 | "location" : "https://github.com/google/GoogleDataTransport.git", 43 | "state" : { 44 | "revision" : "5056b15c5acbb90cd214fe4d6138bdf5a740e5a8", 45 | "version" : "9.2.0" 46 | } 47 | }, 48 | { 49 | "identity" : "googleutilities", 50 | "kind" : "remoteSourceControl", 51 | "location" : "https://github.com/google/GoogleUtilities.git", 52 | "state" : { 53 | "revision" : "68ea347bdb1a69e2d2ae2e25cd085b6ef92f64cb", 54 | "version" : "7.9.0" 55 | } 56 | }, 57 | { 58 | "identity" : "grpc-ios", 59 | "kind" : "remoteSourceControl", 60 | "location" : "https://github.com/grpc/grpc-ios.git", 61 | "state" : { 62 | "revision" : "8440b914756e0d26d4f4d054a1c1581daedfc5b6", 63 | "version" : "1.44.3-grpc" 64 | } 65 | }, 66 | { 67 | "identity" : "gtm-session-fetcher", 68 | "kind" : "remoteSourceControl", 69 | "location" : "https://github.com/google/gtm-session-fetcher.git", 70 | "state" : { 71 | "revision" : "d4289da23e978f37c344ea6a386e5546e2466294", 72 | "version" : "2.1.0" 73 | } 74 | }, 75 | { 76 | "identity" : "leveldb", 77 | "kind" : "remoteSourceControl", 78 | "location" : "https://github.com/firebase/leveldb.git", 79 | "state" : { 80 | "revision" : "0706abcc6b0bd9cedfbb015ba840e4a780b5159b", 81 | "version" : "1.22.2" 82 | } 83 | }, 84 | { 85 | "identity" : "nanopb", 86 | "kind" : "remoteSourceControl", 87 | "location" : "https://github.com/firebase/nanopb.git", 88 | "state" : { 89 | "revision" : "819d0a2173aff699fb8c364b6fb906f7cdb1a692", 90 | "version" : "2.30909.0" 91 | } 92 | }, 93 | { 94 | "identity" : "promises", 95 | "kind" : "remoteSourceControl", 96 | "location" : "https://github.com/google/promises.git", 97 | "state" : { 98 | "revision" : "3e4e743631e86c8c70dbc6efdc7beaa6e90fd3bb", 99 | "version" : "2.1.1" 100 | } 101 | }, 102 | { 103 | "identity" : "swift-protobuf", 104 | "kind" : "remoteSourceControl", 105 | "location" : "https://github.com/apple/swift-protobuf.git", 106 | "state" : { 107 | "revision" : "e1499bc69b9040b29184f7f2996f7bab467c1639", 108 | "version" : "1.19.0" 109 | } 110 | } 111 | ], 112 | "version" : 2 113 | } 114 | -------------------------------------------------------------------------------- /codable/FirestoreCodableSamples.xcodeproj/xcshareddata/xcschemes/FirestoreCodableSamples.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /codable/FirestoreCodableSamples/App/FirestoreCodableSamplesApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FirestoreCodableSamplesApp.swift 3 | // FirestoreCodableSamples 4 | // 5 | // Created by Peter Friese on 29.01.21. 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | import SwiftUI 20 | import FirebaseCore 21 | import FirebaseFirestore 22 | import FirebaseAuth 23 | 24 | @main 25 | struct FirestoreCodableSamplesApp: App { 26 | init() { 27 | FirebaseApp.configure() 28 | 29 | Auth.auth().useEmulator(withHost:"localhost", port:9099) 30 | Auth.auth().signInAnonymously() 31 | 32 | // connect to Firestore Emulator 33 | Firestore.firestore().useEmulator(withHost: "localhost", port: 8080) 34 | let settings = Firestore.firestore().settings 35 | settings.isPersistenceEnabled = false 36 | settings.isSSLEnabled = false 37 | Firestore.firestore().settings = settings 38 | } 39 | 40 | var body: some Scene { 41 | WindowGroup { 42 | NavigationSplitView { 43 | MenuScreen() 44 | } detail: { 45 | Text("Select an item from the menu on the left.") 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /codable/FirestoreCodableSamples/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /codable/FirestoreCodableSamples/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /codable/FirestoreCodableSamples/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /codable/FirestoreCodableSamples/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | 28 | UIApplicationSupportsIndirectInputEvents 29 | 30 | UILaunchScreen 31 | 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | NSAppTransportSecurity 50 | 51 | NSAllowsArbitraryLoads 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /codable/FirestoreCodableSamples/Models/Article.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Article.swift 3 | // FirestoreCodableSamples 4 | // 5 | // Created by Peter Friese on 10.10.22. 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | import Foundation 20 | import SwiftUI 21 | import Firebase 22 | import FirebaseFirestoreSwift 23 | 24 | enum Status: String, CaseIterable, Codable { 25 | case draft 26 | case inReview = "in review" 27 | case approved 28 | case published 29 | } 30 | 31 | extension Status { 32 | var color: Color { 33 | switch self { 34 | case .draft: 35 | return Color.red 36 | case .inReview: 37 | return Color.yellow 38 | case.approved: 39 | return Color.green 40 | case .published: 41 | return Color.blue 42 | } 43 | } 44 | } 45 | 46 | struct Article: Identifiable, Codable { 47 | @DocumentID var id: String? 48 | var title: String 49 | var status: Status 50 | } 51 | 52 | extension Article { 53 | static let empty = Article(title: "", status: .draft) 54 | static let sample = [ 55 | Article(id: "codable", title: "Mapping Firestore Data in Swift - The Comprehensive Guide", status: Status.draft), 56 | Article(id: "lifecycle", title: "Firebase and the new SwiftUI 2 Application Life Cycle", status: Status.approved), 57 | Article(id: "drill-down", title: "SwiftUI Drill-down Navigation", status: .draft), 58 | Article(id: "asyncawait", title: "Using async/await in SwiftUI", status: .published), 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /codable/FirestoreCodableSamples/Models/Book.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Person.swift 3 | // FirestoreCodableSamples 4 | // 5 | // Created by Peter Friese on 29.01.21. 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | import Foundation 20 | import FirebaseFirestoreSwift 21 | 22 | public struct Book: Codable { 23 | @DocumentID var id: String? 24 | var title: String 25 | var numberOfPages: Int 26 | var author: String 27 | } 28 | 29 | extension Book { 30 | static let empty = Book(title: "", numberOfPages: 0, author: "") 31 | } 32 | -------------------------------------------------------------------------------- /codable/FirestoreCodableSamples/Models/BookWithCoverImages.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BookWithComplexType.swift 3 | // FirestoreCodableSamples 4 | // 5 | // Created by Peter Friese on 18.03.21. 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | import Foundation 20 | import FirebaseFirestoreSwift 21 | import SwiftUI 22 | 23 | struct CoverImages: Codable { 24 | var small: URL 25 | var medium: URL 26 | var large: URL 27 | } 28 | 29 | struct BookWithCoverImages: Codable { 30 | @DocumentID var id: String? 31 | var title: String 32 | var numberOfPages: Int 33 | var author: String 34 | var cover: CoverImages? 35 | var color: Color 36 | } 37 | 38 | extension BookWithCoverImages { 39 | static let empty = BookWithCoverImages(title: "", numberOfPages: 0, author: "", cover: nil, color: .black) 40 | } 41 | -------------------------------------------------------------------------------- /codable/FirestoreCodableSamples/Models/BookWithGenre.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BookWithGenre.swift 3 | // FirestoreCodableSamples 4 | // 5 | // Created by Peter Friese on 18.03.21. 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | import Foundation 20 | import FirebaseFirestoreSwift 21 | 22 | public struct BookWithGenre: Codable { 23 | @DocumentID var id: String? 24 | var title: String 25 | var numberOfPages: Int 26 | var author: String 27 | var genres: [String] 28 | } 29 | 30 | extension BookWithGenre { 31 | static let empty = BookWithGenre(title: "", numberOfPages: 0, author: "", genres: []) 32 | static let sample = BookWithGenre(title: "SwiftUI & Combine", numberOfPages: 350, author: "Peter Friese", genres: ["IT", "Programming Languages"]) 33 | } 34 | -------------------------------------------------------------------------------- /codable/FirestoreCodableSamples/Models/BookWithTags.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BookWithTags.swift 3 | // FirestoreCodableSamples 4 | // 5 | // Created by Peter Friese on 18.03.21. 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | import Foundation 20 | import FirebaseFirestoreSwift 21 | import SwiftUI 22 | 23 | struct Tag: Codable, Hashable { 24 | var title: String 25 | var color: Color 26 | } 27 | 28 | struct BookWithTags: Codable { 29 | @DocumentID var id: String? 30 | var title: String 31 | var numberOfPages: Int 32 | var author: String 33 | var tags: [Tag] 34 | } 35 | 36 | extension BookWithTags { 37 | static let empty = BookWithTags(title: "", numberOfPages: 0, author: "", tags: []) 38 | static let sample = BookWithTags(title: "SwiftUI & Combine", 39 | numberOfPages: 350, 40 | author: "Peter Friese", 41 | tags: [ 42 | Tag(title: "Swift", color: Color(hex: "#f05138")), 43 | Tag(title: "SwiftUI", color: Color(hex: "#012495")) 44 | ]) 45 | } 46 | -------------------------------------------------------------------------------- /codable/FirestoreCodableSamples/Models/ColorEntry.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorEntry.swift 3 | // FirestoreCodableSamples 4 | // 5 | // Created by Peter Friese on 19.03.21. 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | import Foundation 20 | import SwiftUI 21 | import FirebaseFirestoreSwift 22 | 23 | struct ColorEntry: Identifiable, Codable { 24 | @DocumentID var id: String? 25 | var name: String 26 | var color: Color 27 | } 28 | 29 | extension ColorEntry { 30 | static var empty = ColorEntry(name: "", color: Color.red) 31 | static var sample = [ 32 | ColorEntry(id: "red", name: "Red", color: .red), 33 | ColorEntry(id: "cerise", name: "Cerise", color: Color(hex: "#d52c67")) 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /codable/FirestoreCodableSamples/Models/Office.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Office.swift 3 | // FirestoreCodableSamples 4 | // 5 | // Created by Peter Friese on 10.10.22. 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | import Foundation 20 | import Firebase 21 | import FirebaseFirestoreSwift 22 | 23 | struct Office: Identifiable, Codable { 24 | @DocumentID var id: String? 25 | var name: String 26 | var location: GeoPoint 27 | } 28 | 29 | extension Office { 30 | static var empty = Office(name: "", location: GeoPoint(latitude: 0, longitude: 0)) 31 | static var sample = [ 32 | Office(id: "6PS", name: "Google UK, 6 Pancras Square", location: GeoPoint(latitude: 51.5327652, longitude: -0.1272025)), 33 | Office(id: "googleplex", name: "Googleplex", location: GeoPoint(latitude: 37.4207294, longitude: -122.084954)), 34 | Office(id: "sfo", name: "Google San Francisco", location: GeoPoint(latitude: 37.7898829, longitude: -122.3915139)), 35 | Office(id: "ham", name: "Google Hamburg", location: GeoPoint(latitude: 53.553986, longitude: 9.9856482)), 36 | Office(id: "nyc", name: "Google New York", location: GeoPoint(latitude: 40.7414688, longitude: -74.0033873)), 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /codable/FirestoreCodableSamples/Models/ProgrammingLanguage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProgrammingLanguage.swift 3 | // FirestoreCodableSamples 4 | // 5 | // Created by Peter Friese on 19.03.21. 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | import Foundation 20 | import FirebaseFirestoreSwift 21 | 22 | struct ProgrammingLanguage: Identifiable, Codable { 23 | @DocumentID var id: String? 24 | var name: String 25 | var year: Date 26 | var reasonWhyILoveThis: String = "" 27 | 28 | enum CodingKeys: String, CodingKey { 29 | case id 30 | case name = "language_name" 31 | case year 32 | } 33 | } 34 | 35 | extension ProgrammingLanguage { 36 | func yearAsString(date: Date) -> String { 37 | let formatter = DateFormatter() 38 | formatter.dateFormat = "yyyy" 39 | return formatter.string(from: date) 40 | } 41 | 42 | var yearAsString: String { 43 | return yearAsString(date: year) 44 | } 45 | } 46 | 47 | extension ProgrammingLanguage { 48 | static let empty = ProgrammingLanguage(name: "", year: Date()) 49 | static let sample = [ 50 | ProgrammingLanguage(id: "plankalkuel", name: "Plankalkül", year: Date(), reasonWhyILoveThis: "It sounds fancy"), 51 | ProgrammingLanguage(id: "swift", name: "Swift", year: Date(), reasonWhyILoveThis: "Extensions & protocols") 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /codable/FirestoreCodableSamples/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /codable/FirestoreCodableSamples/Screens/CustomizeMappingScreen.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomizeMappingScreen.swift 3 | // FirestoreCodableSamples 4 | // 5 | // Created by Peter Friese on 19.03.21. 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | import SwiftUI 20 | import Firebase 21 | import FirebaseFirestoreSwift 22 | 23 | class CustomizeMappingViewModel: ObservableObject { 24 | @Published var programmingLanguages = ProgrammingLanguage.sample // [ProgrammingLanguage]() 25 | @Published var newLanguage = ProgrammingLanguage.empty 26 | @Published var errorMessage: String? 27 | 28 | private var db = Firestore.firestore() 29 | private var listenerRegistration: ListenerRegistration? 30 | 31 | fileprivate func unsubscribe() { 32 | if listenerRegistration != nil { 33 | listenerRegistration?.remove() 34 | listenerRegistration = nil 35 | } 36 | } 37 | 38 | fileprivate func subscribe() { 39 | if listenerRegistration == nil { 40 | listenerRegistration = db.collection("programming-languages") 41 | .addSnapshotListener { [weak self] (querySnapshot, error) in 42 | guard let documents = querySnapshot?.documents else { 43 | self?.errorMessage = "No documents in 'programming-languages' collection" 44 | return 45 | } 46 | 47 | self?.programmingLanguages = documents.compactMap { queryDocumentSnapshot in 48 | let result = Result { try queryDocumentSnapshot.data(as: ProgrammingLanguage.self) } 49 | 50 | switch result { 51 | case .success(let programmingLanguage): 52 | // A ProgrammingLanguage value was successfully initialized from the DocumentSnapshot. 53 | self?.errorMessage = nil 54 | return programmingLanguage 55 | case .failure(let error): 56 | // A Book value could not be initialized from the DocumentSnapshot. 57 | switch error { 58 | case DecodingError.typeMismatch(_, let context): 59 | self?.errorMessage = "\(error.localizedDescription): \(context.debugDescription)" 60 | case DecodingError.valueNotFound(_, let context): 61 | self?.errorMessage = "\(error.localizedDescription): \(context.debugDescription)" 62 | case DecodingError.keyNotFound(_, let context): 63 | self?.errorMessage = "\(error.localizedDescription): \(context.debugDescription)" 64 | case DecodingError.dataCorrupted(let key): 65 | self?.errorMessage = "\(error.localizedDescription): \(key)" 66 | default: 67 | self?.errorMessage = "Error decoding document: \(error.localizedDescription)" 68 | } 69 | return nil 70 | } 71 | } 72 | } 73 | } 74 | } 75 | 76 | fileprivate func addLanguage() { 77 | let collectionRef = db.collection("programming-languages") 78 | do { 79 | let newDocReference = try collectionRef.addDocument(from: newLanguage) 80 | print("ProgrammingLanguage stored with new document reference: \(newDocReference)") 81 | } 82 | catch { 83 | print(error) 84 | } 85 | } 86 | } 87 | 88 | struct CustomizeMappingScreen: View { 89 | @StateObject var viewModel = CustomizeMappingViewModel() 90 | 91 | var body: some View { 92 | SampleScreen("Customize Mapping", introduction: "We can use Codable's CodingKeys to customize Firestore's mapping") { 93 | Form { 94 | Section(header: Text("Programming Languages")) { 95 | List(viewModel.programmingLanguages) { language in 96 | HStack { 97 | Text(language.name) 98 | Spacer() 99 | Text("Released: \(language.yearAsString)") 100 | .font(.caption) 101 | } 102 | } 103 | } 104 | Section { 105 | TextField("Language name", text: $viewModel.newLanguage.name) 106 | TextField("Reason I love this", text: $viewModel.newLanguage.reasonWhyILoveThis) 107 | DatePicker("Date of release", selection: $viewModel.newLanguage.year, displayedComponents: [.date]) 108 | Button(action: viewModel.addLanguage ) { 109 | Label("Add new language", systemImage: "plus") 110 | } 111 | } 112 | } 113 | } 114 | .onAppear() { 115 | viewModel.subscribe() 116 | } 117 | .onDisappear() { 118 | viewModel.unsubscribe() 119 | } 120 | } 121 | } 122 | 123 | struct CustomizeMappingScreen_Previews: PreviewProvider { 124 | static var previews: some View { 125 | Group { 126 | NavigationView { 127 | CustomizeMappingScreen() 128 | } 129 | NavigationView { 130 | CustomizeMappingScreen() 131 | } 132 | .preferredColorScheme(.dark) 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /codable/FirestoreCodableSamples/Screens/ManuallyMappingSimpleTypesScreen.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ManuallyMappingSimpleTypesScreen.swift 3 | // FirestoreCodableSamples 4 | // 5 | // Created by Peter Friese on 18.03.21. 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | import SwiftUI 20 | import Firebase 21 | import FirebaseFirestoreSwift 22 | 23 | class ManuallyMappingSimpleTypesViewModel: ObservableObject { 24 | @Published var book: Book = .empty 25 | @Published var errorMessage: String? 26 | 27 | private var db = Firestore.firestore() 28 | 29 | fileprivate func fetchAndMap() { 30 | fetchBook(documentId: "hitchhiker") 31 | } 32 | 33 | fileprivate func fetchAndMapNonExisting() { 34 | fetchBook(documentId: "does-not-exist") 35 | } 36 | 37 | fileprivate func fetchAndTryMappingInvalidData() { 38 | fetchBook(documentId: "invalid-data") 39 | } 40 | 41 | #warning("DO NOT MAP YOUR DOCUMENTS MANUALLY. USE CODABLE INSTEAD.") 42 | private func fetchBook(documentId: String) { 43 | let docRef = db.collection("books").document(documentId) 44 | 45 | docRef.getDocument { document, error in 46 | if let error = error as NSError? { 47 | self.errorMessage = "Error getting document: \(error.localizedDescription)" 48 | } 49 | else { 50 | if let document = document { 51 | let id = document.documentID 52 | let data = document.data() 53 | let title = data?["title"] as? String ?? "" 54 | let numberOfPages = data?["numberOfPages"] as? Int ?? 0 55 | let author = data?["author"] as? String ?? "" 56 | self.book = Book(id:id, title: title, numberOfPages: numberOfPages, author: author) 57 | } 58 | } 59 | } 60 | } 61 | } 62 | 63 | struct ManuallyMappingSimpleTypesScreen: View { 64 | @StateObject var viewModel = ManuallyMappingSimpleTypesViewModel() 65 | 66 | @ViewBuilder 67 | var messageView: some View { 68 | if let errorMessage = viewModel.errorMessage { 69 | Text(errorMessage).foregroundColor(.red) 70 | } 71 | else { 72 | EmptyView() 73 | } 74 | } 75 | 76 | var body: some View { 77 | SampleScreen("Mapping Simple Types", introduction: "⚠️ Mapping documents manually is possible, but not recommended.") { 78 | Form { 79 | Section(header: Text("Book"), footer: messageView) { 80 | TextField("Title", text: $viewModel.book.title) 81 | TextField("Number of pages", value: $viewModel.book.numberOfPages, formatter: NumberFormatter()) 82 | TextField("Author", text: $viewModel.book.author) 83 | } 84 | Section(header: Text("Actions")) { 85 | Button("Fetch and map a book") { 86 | viewModel.fetchAndMap() 87 | } 88 | Button("Fetch and map a non-existing book") { 89 | viewModel.fetchAndMapNonExisting() 90 | } 91 | Button("Try mapping book from invalid data") { 92 | viewModel.fetchAndTryMappingInvalidData() 93 | } 94 | } 95 | } 96 | } 97 | } 98 | } 99 | 100 | struct ManuallyMappingSimpleTypesScreen_Previews: PreviewProvider { 101 | static var previews: some View { 102 | NavigationView { 103 | ManuallyMappingSimpleTypesScreen() 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /codable/FirestoreCodableSamples/Screens/MappingArraysScreen.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MappingArraysSCreen.swift 3 | // FirestoreCodableSamples 4 | // 5 | // Created by Peter Friese on 18.03.21. 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | import SwiftUI 20 | import Firebase 21 | import FirebaseFirestoreSwift 22 | 23 | class MappingArraysViewModel: ObservableObject { 24 | @Published var book: BookWithGenre = .empty 25 | @Published var errorMessage: String? 26 | 27 | private var db = Firestore.firestore() 28 | 29 | fileprivate func fetchAndMap() { 30 | fetchBook(documentId: "hitchhiker-genre") 31 | } 32 | 33 | private func fetchBook(documentId: String) { 34 | let docRef = db.collection("books").document(documentId) 35 | 36 | docRef.getDocument(as: BookWithGenre.self) { result in 37 | switch result { 38 | case .success(let book): 39 | self.book = book 40 | self.errorMessage = nil 41 | case .failure(let error): 42 | // A Book value could not be initialized from the DocumentSnapshot. 43 | switch error { 44 | case DecodingError.typeMismatch(_, let context): 45 | self.errorMessage = "\(error.localizedDescription): \(context.debugDescription)" 46 | case DecodingError.valueNotFound(_, let context): 47 | self.errorMessage = "\(error.localizedDescription): \(context.debugDescription)" 48 | case DecodingError.keyNotFound(_, let context): 49 | self.errorMessage = "\(error.localizedDescription): \(context.debugDescription)" 50 | case DecodingError.dataCorrupted(let key): 51 | self.errorMessage = "\(error.localizedDescription): \(key)" 52 | default: 53 | self.errorMessage = "Error decoding document: \(error.localizedDescription)" 54 | } 55 | } 56 | } 57 | } 58 | 59 | fileprivate func updateBook() { 60 | if let id = book.id { 61 | let docRef = db.collection("books").document(id) 62 | do { 63 | try docRef.setData(from: book) 64 | } 65 | catch { 66 | print(error) 67 | } 68 | } 69 | } 70 | 71 | fileprivate func addBook() { 72 | let collectionRef = db.collection("books") 73 | do { 74 | let newDocReference = try collectionRef.addDocument(from: self.book) 75 | print("Book stored with new document reference: \(newDocReference)") 76 | } 77 | catch { 78 | print(error) 79 | } 80 | } 81 | 82 | func newBook() { 83 | self.book = .empty 84 | } 85 | 86 | private var genres: Set = ["Action", "Fantasy", "Fairy Tale"] 87 | 88 | func addGenre() { 89 | let unusedGenres = self.genres.subtracting(book.genres) 90 | if let genre = unusedGenres.first { 91 | self.book.genres.append(genre) 92 | } 93 | } 94 | } 95 | 96 | struct MappingArraysScreen: View { 97 | @StateObject var viewModel = MappingArraysViewModel() 98 | 99 | @ViewBuilder 100 | var messageView: some View { 101 | if let errorMessage = viewModel.errorMessage { 102 | Text(errorMessage).foregroundColor(.red) 103 | } 104 | else { 105 | EmptyView() 106 | } 107 | } 108 | 109 | var body: some View { 110 | SampleScreen("Mapping Arrays", introduction: "Firestore makes mapping arrays easy.") { 111 | Form { 112 | Section(header: Text("Book"), footer: messageView) { 113 | TextField("Title", text: $viewModel.book.title) 114 | TextField("Number of pages", value: $viewModel.book.numberOfPages, formatter: NumberFormatter()) 115 | TextField("Author", text: $viewModel.book.author) 116 | } 117 | Section(header: Text("Genres")) { 118 | List(viewModel.book.genres, id: \.self) { genre in 119 | Text(genre) 120 | } 121 | Button(action: viewModel.addGenre) { 122 | Label("Add a genre", systemImage: "plus") 123 | } 124 | } 125 | Section(header: Text("Actions")) { 126 | Button(action: viewModel.fetchAndMap) { 127 | Label("Fetch and map a book", systemImage: "square.and.arrow.up") 128 | } 129 | 130 | Button(action: viewModel.updateBook) { 131 | Label("Udpate book", systemImage: "square.and.arrow.down") 132 | } 133 | Button(action: viewModel.newBook) { 134 | Label("New Book", systemImage: "sparkles") 135 | } 136 | Button(action: viewModel.addBook) { 137 | Label("Add Book", systemImage: "plus") 138 | } 139 | } 140 | } 141 | } 142 | } 143 | } 144 | 145 | struct MappingArraysScreen_Previews: PreviewProvider { 146 | static var previews: some View { 147 | NavigationView { 148 | MappingArraysScreen() 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /codable/FirestoreCodableSamples/Screens/MappingArraysWithNestedTypesScreen.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MappingArraysWithNestedTypes.swift 3 | // FirestoreCodableSamples 4 | // 5 | // Created by Peter Friese on 18.03.21. 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | import SwiftUI 20 | 21 | import SwiftUI 22 | import Firebase 23 | import FirebaseFirestoreSwift 24 | 25 | class MappingArraysWithNestedTypesViewModel: ObservableObject { 26 | @Published var book: BookWithTags = .empty 27 | @Published var errorMessage: String? 28 | 29 | private var db = Firestore.firestore() 30 | 31 | fileprivate func fetchAndMap() { 32 | fetchBook(documentId: "hitchhiker-tags") 33 | } 34 | 35 | private func fetchBook(documentId: String) { 36 | let docRef = db.collection("books").document(documentId) 37 | 38 | docRef.getDocument(as: BookWithTags.self) { result in 39 | switch result { 40 | case .success(let book): 41 | self.book = book 42 | self.errorMessage = nil 43 | case .failure(let error): 44 | // A Book value could not be initialized from the DocumentSnapshot. 45 | switch error { 46 | case DecodingError.typeMismatch(_, let context): 47 | self.errorMessage = "\(error.localizedDescription): \(context.debugDescription)" 48 | case DecodingError.valueNotFound(_, let context): 49 | self.errorMessage = "\(error.localizedDescription): \(context.debugDescription)" 50 | case DecodingError.keyNotFound(_, let context): 51 | self.errorMessage = "\(error.localizedDescription): \(context.debugDescription)" 52 | case DecodingError.dataCorrupted(let key): 53 | self.errorMessage = "\(error.localizedDescription): \(key)" 54 | default: 55 | self.errorMessage = "Error decoding document: \(error.localizedDescription)" 56 | } 57 | } 58 | } 59 | } 60 | 61 | fileprivate func updateBook() { 62 | if let id = book.id { 63 | let docRef = db.collection("books").document(id) 64 | do { 65 | try docRef.setData(from: book) 66 | } 67 | catch { 68 | print(error) 69 | } 70 | } 71 | } 72 | 73 | fileprivate func addBook() { 74 | let collectionRef = db.collection("books") 75 | do { 76 | let newDocReference = try collectionRef.addDocument(from: self.book) 77 | print("Book stored with new document reference: \(newDocReference)") 78 | } 79 | catch { 80 | print(error) 81 | } 82 | } 83 | 84 | fileprivate func newBook() { 85 | self.book = .empty 86 | } 87 | 88 | private var tags: Set = [ 89 | Tag(title: "Space", color: Color(hex: "#131a2d")), 90 | Tag(title: "Tea", color: Color(hex: "#d8bfbf")), 91 | Tag(title: "Improbability", color: Color(hex: "#fd1d48")), 92 | ] 93 | 94 | fileprivate func addTag() { 95 | let unusedTags = self.tags.subtracting(book.tags) 96 | if let tag = unusedTags.first { 97 | self.book.tags.append(tag) 98 | } 99 | } 100 | } 101 | 102 | struct MappingArraysWithNestedTypesScreen: View { 103 | @StateObject var viewModel = MappingArraysWithNestedTypesViewModel() 104 | 105 | @ViewBuilder 106 | var messageView: some View { 107 | if let errorMessage = viewModel.errorMessage { 108 | Text(errorMessage).foregroundColor(.red) 109 | } 110 | else { 111 | EmptyView() 112 | } 113 | } 114 | 115 | var body: some View { 116 | SampleScreen("Mapping Arrays", introduction: "Firestore makes mapping arrays easy.") { 117 | Form { 118 | Section(header: Text("Book"), footer: messageView) { 119 | TextField("Title", text: $viewModel.book.title) 120 | TextField("Number of pages", value: $viewModel.book.numberOfPages, formatter: NumberFormatter()) 121 | TextField("Author", text: $viewModel.book.author) 122 | } 123 | Section(header: Text("Tags")) { 124 | List(viewModel.book.tags, id: \.self) { tag in 125 | Text(tag.title) 126 | .foregroundColor(tag.color.accessibleFontColor) 127 | .padding(.horizontal, 4) 128 | .padding(.vertical, 2) 129 | .background(tag.color) 130 | .cornerRadius(3.0) 131 | } 132 | Button(action: viewModel.addTag) { 133 | Label("Add a tag", systemImage: "plus") 134 | } 135 | } 136 | Section(header: Text("Actions")) { 137 | Button(action: viewModel.fetchAndMap) { 138 | Label("Fetch and map a book", systemImage: "square.and.arrow.up") 139 | } 140 | 141 | Button(action: viewModel.updateBook) { 142 | Label("Udpate book", systemImage: "square.and.arrow.down") 143 | } 144 | Button(action: viewModel.newBook) { 145 | Label("New Book", systemImage: "sparkles") 146 | } 147 | Button(action: viewModel.addBook) { 148 | Label("Add Book", systemImage: "plus") 149 | } 150 | } 151 | } 152 | } 153 | } 154 | } 155 | 156 | struct MappingArraysWithNestedTypesScreen_Previews: PreviewProvider { 157 | static var previews: some View { 158 | NavigationView { 159 | MappingArraysWithNestedTypesScreen() 160 | } 161 | } 162 | } 163 | 164 | -------------------------------------------------------------------------------- /codable/FirestoreCodableSamples/Screens/MappingColorsScreen.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MappingColorsSCreen.swift 3 | // FirestoreCodableSamples 4 | // 5 | // Created by Peter Friese on 19.03.21. 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | import SwiftUI 20 | import Firebase 21 | import FirebaseFirestoreSwift 22 | 23 | class MappingColorsViewModel: ObservableObject { 24 | @Published var colorEntries = [ColorEntry]() 25 | @Published var newColor = ColorEntry.empty 26 | @Published var errorMessage: String? 27 | 28 | private var db = Firestore.firestore() 29 | private var listenerRegistration: ListenerRegistration? 30 | 31 | fileprivate func unsubscribe() { 32 | if listenerRegistration != nil { 33 | listenerRegistration?.remove() 34 | listenerRegistration = nil 35 | } 36 | } 37 | 38 | fileprivate func subscribe() { 39 | if listenerRegistration == nil { 40 | listenerRegistration = db.collection("colors") 41 | .addSnapshotListener { [weak self] (querySnapshot, error) in 42 | guard let documents = querySnapshot?.documents else { 43 | self?.errorMessage = "No documents in 'colors' collection" 44 | return 45 | } 46 | 47 | self?.colorEntries = documents.compactMap { queryDocumentSnapshot in 48 | let result = Result { try queryDocumentSnapshot.data(as: ColorEntry.self) } 49 | switch result { 50 | case .success(let colorEntry): 51 | // A ColorEntry value was successfully initialized from the DocumentSnapshot. 52 | self?.errorMessage = nil 53 | return colorEntry 54 | case .failure(let error): 55 | // A ColorEntry value could not be initialized from the DocumentSnapshot. 56 | switch error { 57 | case DecodingError.typeMismatch(_, let context): 58 | self?.errorMessage = "\(error.localizedDescription): \(context.debugDescription)" 59 | case DecodingError.valueNotFound(_, let context): 60 | self?.errorMessage = "\(error.localizedDescription): \(context.debugDescription)" 61 | case DecodingError.keyNotFound(_, let context): 62 | self?.errorMessage = "\(error.localizedDescription): \(context.debugDescription)" 63 | case DecodingError.dataCorrupted(let key): 64 | self?.errorMessage = "\(error.localizedDescription): \(key)" 65 | default: 66 | self?.errorMessage = "Error decoding document: \(error.localizedDescription)" 67 | } 68 | return nil 69 | } 70 | } 71 | } 72 | } 73 | } 74 | 75 | fileprivate func addColorEntry() { 76 | let collectionRef = db.collection("colors") 77 | do { 78 | let newDocReference = try collectionRef.addDocument(from: newColor) 79 | print("ColorEntry stored with new document reference: \(newDocReference)") 80 | } 81 | catch { 82 | print(error) 83 | } 84 | } 85 | } 86 | 87 | struct MappingColorsScreen: View { 88 | @StateObject var viewModel = MappingColorsViewModel() 89 | 90 | var body: some View { 91 | SampleScreen("Mapping Colors", introduction: "Mapping colors is easy once we conform Color to Codable.") { 92 | Form { 93 | Section(header: Text("Colors")) { 94 | List(viewModel.colorEntries) { colorEntry in 95 | Text("\(colorEntry.name) (\(colorEntry.color.toHex ?? ""))") 96 | .listRowBackground(colorEntry.color) 97 | .foregroundColor(colorEntry.color.accessibleFontColor) 98 | .padding(.horizontal, 4) 99 | .padding(.vertical, 2) 100 | .background(colorEntry.color) 101 | .cornerRadius(3.0) 102 | } 103 | } 104 | Section { 105 | ColorPicker(selection: $viewModel.newColor.color) { 106 | Label("First, pick color", systemImage: "paintpalette") 107 | } 108 | Button(action: viewModel.addColorEntry ) { 109 | Label("Then add it", systemImage: "plus") 110 | } 111 | } 112 | } 113 | } 114 | .onAppear() { 115 | viewModel.subscribe() 116 | } 117 | .onDisappear() { 118 | viewModel.unsubscribe() 119 | } 120 | } 121 | } 122 | 123 | struct MappingColorsScreen_Previews: PreviewProvider { 124 | static var previews: some View { 125 | Group { 126 | NavigationView { 127 | MappingColorsScreen() 128 | } 129 | NavigationView { 130 | MappingColorsScreen() 131 | } 132 | .preferredColorScheme(.dark) 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /codable/FirestoreCodableSamples/Screens/MappingCustomTypesScreen.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MappingCustomTypesScreen.swift 3 | // FirestoreCodableSamples 4 | // 5 | // Created by Peter Friese on 18.03.21. 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | import SwiftUI 20 | import Firebase 21 | import FirebaseFirestoreSwift 22 | 23 | class MappingCustomTypesViewModel: ObservableObject { 24 | @Published var book: BookWithCoverImages = .empty 25 | @Published var errorMessage: String? 26 | 27 | private var db = Firestore.firestore() 28 | 29 | fileprivate func fetchAndMap() { 30 | fetchBook(documentId: "hitchhiker-image-urls") 31 | } 32 | 33 | private func fetchBook(documentId: String) { 34 | let docRef = db.collection("books").document(documentId) 35 | 36 | docRef.getDocument(as: BookWithCoverImages.self) { result in 37 | switch result { 38 | case .success(let book): 39 | // A Book value was successfully initialized from the DocumentSnapshot. 40 | self.book = book 41 | self.errorMessage = nil 42 | case .failure(let error): 43 | // A Book value could not be initialized from the DocumentSnapshot. 44 | switch error { 45 | case DecodingError.typeMismatch(_, let context): 46 | self.errorMessage = "\(error.localizedDescription): \(context.debugDescription)" 47 | case DecodingError.valueNotFound(_, let context): 48 | self.errorMessage = "\(error.localizedDescription): \(context.debugDescription)" 49 | case DecodingError.keyNotFound(_, let context): 50 | self.errorMessage = "\(error.localizedDescription): \(context.debugDescription)" 51 | case DecodingError.dataCorrupted(let key): 52 | self.errorMessage = "\(error.localizedDescription): \(key)" 53 | default: 54 | self.errorMessage = "Error decoding document: \(error.localizedDescription)" 55 | } 56 | } 57 | } 58 | } 59 | 60 | fileprivate func updateBook() { 61 | if let id = book.id { 62 | let docRef = db.collection("books").document(id) 63 | do { 64 | try docRef.setData(from: book) 65 | } 66 | catch { 67 | print(error) 68 | } 69 | } 70 | } 71 | 72 | fileprivate func addBook() { 73 | let collectionRef = db.collection("books") 74 | do { 75 | let newDocReference = try collectionRef.addDocument(from: self.book) 76 | print("Book stored with new document reference: \(newDocReference)") 77 | } 78 | catch { 79 | print(error) 80 | } 81 | } 82 | } 83 | 84 | struct MappingCustomTypesScreen: View { 85 | @StateObject var viewModel = MappingCustomTypesViewModel() 86 | 87 | @ViewBuilder 88 | var messageView: some View { 89 | if let errorMessage = viewModel.errorMessage { 90 | Text(errorMessage).foregroundColor(.red) 91 | } 92 | else { 93 | EmptyView() 94 | } 95 | } 96 | 97 | var body: some View { 98 | SampleScreen("Mapping Simple Types", introduction: "Firestore makes mapping simple data types as simple as possible.") { 99 | Form { 100 | Section(header: Text("Book"), footer: messageView) { 101 | TextField("Title", text: $viewModel.book.title) 102 | TextField("Number of pages", value: $viewModel.book.numberOfPages, formatter: NumberFormatter()) 103 | TextField("Author", text: $viewModel.book.author) 104 | NavigationLink(destination: CoverImagesScreen(covers: viewModel.book.cover)) { 105 | Text("Covers") 106 | } 107 | } 108 | Section(header: Text("Actions")) { 109 | Button(action: viewModel.fetchAndMap) { 110 | Label("Fetch and map a book", systemImage: "square.and.arrow.up") 111 | } 112 | 113 | Button(action: viewModel.updateBook) { 114 | Label("Udpate book", systemImage: "square.and.arrow.down") 115 | } 116 | Button(action: viewModel.addBook) { 117 | Label("Add Book", systemImage: "plus") 118 | } 119 | } 120 | } 121 | } 122 | } 123 | } 124 | 125 | struct CoverImagesScreen: View { 126 | var covers: CoverImages? 127 | var body: some View { 128 | if let covers = covers { 129 | List { 130 | Text(covers.small.absoluteString) 131 | Text(covers.medium.absoluteString) 132 | Text(covers.large.absoluteString) 133 | } 134 | } 135 | else { 136 | Text("This book has no cover images") 137 | } 138 | } 139 | } 140 | 141 | struct MappingCustomTypesScreen_Previews: PreviewProvider { 142 | static var previews: some View { 143 | NavigationView { 144 | MappingCustomTypesScreen() 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /codable/FirestoreCodableSamples/Screens/MappingEnumsScreen.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MappingEnumsScreen.swift 3 | // FirestoreCodableSamples 4 | // 5 | // Created by Peter Friese on 22.03.21. 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | import SwiftUI 20 | import Firebase 21 | import FirebaseFirestoreSwift 22 | 23 | 24 | 25 | class MappingEnumsViewModel: ObservableObject { 26 | @Published var articles = Article.sample // [Article]() 27 | @Published var newArticle = Article.empty 28 | @Published var errorMessage: String? 29 | 30 | private var db = Firestore.firestore() 31 | private var listenerRegistration: ListenerRegistration? 32 | 33 | fileprivate func unsubscribe() { 34 | if listenerRegistration != nil { 35 | listenerRegistration?.remove() 36 | listenerRegistration = nil 37 | } 38 | } 39 | 40 | fileprivate func subscribe() { 41 | if listenerRegistration == nil { 42 | listenerRegistration = db.collection("articles") 43 | .addSnapshotListener { [weak self] (querySnapshot, error) in 44 | guard let documents = querySnapshot?.documents else { 45 | self?.errorMessage = "No documents in 'articles' collection" 46 | return 47 | } 48 | 49 | self?.articles = documents.compactMap { queryDocumentSnapshot in 50 | let result = Result { try queryDocumentSnapshot.data(as: Article.self) } 51 | 52 | switch result { 53 | case .success(let article): 54 | // An Article value was successfully initialized from the DocumentSnapshot. 55 | self?.errorMessage = nil 56 | return article 57 | case .failure(let error): 58 | // An Article value could not be initialized from the DocumentSnapshot. 59 | switch error { 60 | case DecodingError.typeMismatch(_, let context): 61 | self?.errorMessage = "\(error.localizedDescription): \(context.debugDescription)" 62 | case DecodingError.valueNotFound(_, let context): 63 | self?.errorMessage = "\(error.localizedDescription): \(context.debugDescription)" 64 | case DecodingError.keyNotFound(_, let context): 65 | self?.errorMessage = "\(error.localizedDescription): \(context.debugDescription)" 66 | case DecodingError.dataCorrupted(let key): 67 | self?.errorMessage = "\(error.localizedDescription): \(key)" 68 | default: 69 | self?.errorMessage = "Error decoding document: \(error.localizedDescription)" 70 | } 71 | return nil 72 | } 73 | } 74 | } 75 | } 76 | } 77 | 78 | fileprivate func addArticle() { 79 | let collectionRef = db.collection("articles") 80 | do { 81 | let newDocReference = try collectionRef.addDocument(from: newArticle) 82 | print("Article stored with new document reference: \(newDocReference)") 83 | } 84 | catch { 85 | print(error) 86 | } 87 | } 88 | } 89 | 90 | struct MappingEnumsScreen: View { 91 | @StateObject var viewModel = MappingEnumsViewModel() 92 | 93 | var body: some View { 94 | SampleScreen("Mapping Enums", introduction: "Enums can be mapped easily if their elements are codable") { 95 | Form { 96 | Section(header: Text("Articles")) { 97 | List(viewModel.articles) { article in 98 | VStack(alignment: .leading) { 99 | Text(article.title) 100 | Text("Status: \(article.status.rawValue)") 101 | .foregroundColor(article.status.color.accessibleFontColor) 102 | .padding(.horizontal, 4) 103 | .padding(.vertical, 2) 104 | .background(article.status.color) 105 | .cornerRadius(3.0) 106 | } 107 | } 108 | } 109 | Section { 110 | TextField("Title", text: $viewModel.newArticle.title) 111 | Picker("Status", selection: $viewModel.newArticle.status) { 112 | ForEach(Status.allCases, id: \.self) { status in 113 | Text(status.rawValue) 114 | } 115 | } 116 | Button(action: viewModel.addArticle ) { 117 | Label("Add new article", systemImage: "plus") 118 | } 119 | } 120 | } 121 | } 122 | .onAppear() { 123 | viewModel.subscribe() 124 | } 125 | .onDisappear() { 126 | viewModel.unsubscribe() 127 | } 128 | } 129 | } 130 | 131 | struct MappingEnumsScreen_Previews: PreviewProvider { 132 | static var previews: some View { 133 | NavigationView { 134 | MappingEnumsScreen() 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /codable/FirestoreCodableSamples/Screens/MappingGeoPointsScreen.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MappingGeoPointsScreen.swift 3 | // FirestoreCodableSamples 4 | // 5 | // Created by Peter Friese on 22.03.21. 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | import SwiftUI 20 | import MapKit 21 | import Firebase 22 | import FirebaseFirestoreSwift 23 | 24 | class MappingGeoPointsViewModel: ObservableObject { 25 | @Published var offices = Office.sample // [Office]() 26 | @Published var selectedOffice = Office.sample[0] 27 | @Published var selectedOfficeCoordinate: CLLocationCoordinate2D? 28 | @Published var visibleRegion: MKCoordinateRegion! 29 | @Published var errorMessage: String? 30 | 31 | private var db = Firestore.firestore() 32 | private var listenerRegistration: ListenerRegistration? 33 | 34 | init() { 35 | $selectedOffice 36 | // .compactMap { $0 } 37 | .map { office in 38 | CLLocationCoordinate2D(latitude: office.location.latitude, 39 | longitude: office.location.longitude) 40 | } 41 | .assign(to: &$selectedOfficeCoordinate) 42 | 43 | $selectedOfficeCoordinate 44 | .compactMap { $0 } 45 | .map { officeCoordinate in 46 | MKCoordinateRegion(center: officeCoordinate, 47 | span: MKCoordinateSpan( 48 | latitudeDelta: 0.005, 49 | longitudeDelta: 0.005)) 50 | } 51 | .assign(to: &$visibleRegion) 52 | 53 | 54 | 55 | } 56 | 57 | fileprivate func unsubscribe() { 58 | if listenerRegistration != nil { 59 | listenerRegistration?.remove() 60 | listenerRegistration = nil 61 | } 62 | } 63 | 64 | fileprivate func subscribe() { 65 | if listenerRegistration == nil { 66 | listenerRegistration = db.collection("locations") 67 | .addSnapshotListener { [weak self] (querySnapshot, error) in 68 | guard let documents = querySnapshot?.documents else { 69 | self?.errorMessage = "No documents in the 'locations' collection" 70 | return 71 | } 72 | 73 | self?.offices = documents.compactMap { queryDocumentSnapshot in 74 | let result = Result { try queryDocumentSnapshot.data(as: Office.self) } 75 | 76 | switch result { 77 | case .success(let office): 78 | // An Office value was successfully initialized from the DocumentSnapshot. 79 | self?.errorMessage = nil 80 | return office 81 | case .failure(let error): 82 | // An Office value could not be initialized from the DocumentSnapshot. 83 | switch error { 84 | case DecodingError.typeMismatch(_, let context): 85 | self?.errorMessage = "\(error.localizedDescription): \(context.debugDescription)" 86 | case DecodingError.valueNotFound(_, let context): 87 | self?.errorMessage = "\(error.localizedDescription): \(context.debugDescription)" 88 | case DecodingError.keyNotFound(_, let context): 89 | self?.errorMessage = "\(error.localizedDescription): \(context.debugDescription)" 90 | case DecodingError.dataCorrupted(let key): 91 | self?.errorMessage = "\(error.localizedDescription): \(key)" 92 | default: 93 | self?.errorMessage = "Error decoding document: \(error.localizedDescription)" 94 | } 95 | return nil 96 | } 97 | } 98 | } 99 | } 100 | } 101 | } 102 | 103 | struct MappingGeoPointsScreen: View { 104 | @StateObject var viewModel = MappingGeoPointsViewModel() 105 | 106 | var body: some View { 107 | SampleScreen("Mapping Geopoints", introduction: "GeoPoints are supported natively in Firestore") { 108 | Form { 109 | Map(coordinateRegion: $viewModel.visibleRegion, annotationItems: viewModel.offices) { office in 110 | MapPin(coordinate: CLLocationCoordinate2D(latitude: office.location.latitude, longitude: office.location.longitude), tint: .red) 111 | } 112 | .frame(height: 300) 113 | Section(header: Text("Google Offices")) { 114 | List(viewModel.offices) { office in 115 | VStack(alignment: .leading) { 116 | Text(office.name) 117 | Text("Lat: \(office.location.latitude), Lon: \(office.location.longitude)") 118 | } 119 | .onTapGesture { 120 | viewModel.selectedOffice = office 121 | } 122 | } 123 | } 124 | } 125 | } 126 | .onAppear() { 127 | viewModel.subscribe() 128 | } 129 | .onDisappear() { 130 | viewModel.unsubscribe() 131 | } 132 | } 133 | } 134 | 135 | struct MappingGeoPointsScreen_Previews: PreviewProvider { 136 | static var previews: some View { 137 | NavigationView { 138 | MappingGeoPointsScreen() 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /codable/FirestoreCodableSamples/Screens/MappingSimpleTypesScreen.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MappingSimpleTypesScreen.swift 3 | // FirestoreCodableSamples 4 | // 5 | // Created by Peter Friese on 17.03.21. 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | import SwiftUI 20 | import Firebase 21 | import FirebaseFirestoreSwift 22 | 23 | class MappingSimpleTypesViewModel: ObservableObject { 24 | @Published var book: Book = .empty 25 | @Published var errorMessage: String? 26 | 27 | private var db = Firestore.firestore() 28 | 29 | /// Use this to alteratively use async/await or callback-based implementations 30 | let useAsync = false 31 | 32 | fileprivate func fetchAndMap() { 33 | if useAsync { 34 | Task { 35 | await fetchBookAsync(documentId: "hitchhiker") 36 | } 37 | } 38 | else { 39 | fetchBook(documentId: "hitchhiker") 40 | } 41 | } 42 | 43 | fileprivate func fetchAndMapNonExisting() { 44 | if useAsync { 45 | Task { 46 | await fetchBookAsync(documentId: "does-not-exist") 47 | } 48 | } 49 | else { 50 | fetchBook(documentId: "does-not-exist") 51 | } 52 | } 53 | 54 | fileprivate func fetchAndTryMappingInvalidData() { 55 | if useAsync { 56 | Task { 57 | await fetchBookAsync(documentId: "invalid-data") 58 | } 59 | } 60 | else { 61 | fetchBook(documentId: "invalid-data") 62 | } 63 | } 64 | 65 | /// Alternative implementation that shows how to use async/await to call `getDocument` 66 | /// This needs to be marked as @MainActor so that we can safely access the errorMessage 67 | /// published property when encountering an error. 68 | @MainActor 69 | private func fetchBookAsync(documentId: String) async { 70 | let docRef = db.collection("books").document(documentId) 71 | do { 72 | self.book = try await docRef.getDocument(as: Book.self) 73 | } 74 | catch { 75 | switch error { 76 | case DecodingError.typeMismatch(_, let context): 77 | self.errorMessage = "\(error.localizedDescription): \(context.debugDescription)" 78 | case DecodingError.valueNotFound(_, let context): 79 | self.errorMessage = "\(error.localizedDescription): \(context.debugDescription)" 80 | case DecodingError.keyNotFound(_, let context): 81 | self.errorMessage = "\(error.localizedDescription): \(context.debugDescription)" 82 | case DecodingError.dataCorrupted(let key): 83 | self.errorMessage = "\(error.localizedDescription): \(key)" 84 | default: 85 | self.errorMessage = "Error decoding document: \(error.localizedDescription)" 86 | } 87 | } 88 | } 89 | 90 | private func fetchBook(documentId: String) { 91 | let docRef = db.collection("books").document(documentId) 92 | 93 | docRef.getDocument(as: Book.self) { result in 94 | switch result { 95 | case .success(let book): 96 | // A Book value was successfully initialized from the DocumentSnapshot. 97 | self.book = book 98 | self.errorMessage = nil 99 | case .failure(let error): 100 | // A Book value could not be initialized from the DocumentSnapshot. 101 | switch error { 102 | case DecodingError.typeMismatch(_, let context): 103 | self.errorMessage = "\(error.localizedDescription): \(context.debugDescription)" 104 | case DecodingError.valueNotFound(_, let context): 105 | self.errorMessage = "\(error.localizedDescription): \(context.debugDescription)" 106 | case DecodingError.keyNotFound(_, let context): 107 | self.errorMessage = "\(error.localizedDescription): \(context.debugDescription)" 108 | case DecodingError.dataCorrupted(let key): 109 | self.errorMessage = "\(error.localizedDescription): \(key)" 110 | default: 111 | self.errorMessage = "Error decoding document: \(error.localizedDescription)" 112 | } 113 | } 114 | } 115 | } 116 | 117 | private func fetchBookOptional(documentId: String) { 118 | let docRef = db.collection("books").document(documentId) 119 | 120 | // If you expect that a document might *not exist*, use an optional type (Book?.self) 121 | // and then perform an `if let book = book` dance to handle this case. 122 | docRef.getDocument(as: Book?.self) { result in 123 | switch result { 124 | case .success(let book): 125 | if let book = book { 126 | // A Book value was successfully initialized from the DocumentSnapshot. 127 | self.book = book 128 | self.errorMessage = nil 129 | } 130 | else { 131 | // A nil value was successfully initialized from the DocumentSnapshot, 132 | // or the DocumentSnapshot was nil. 133 | self.errorMessage = "Document doesn't exist." 134 | } 135 | case .failure(let error): 136 | // A Book value could not be initialized from the DocumentSnapshot. 137 | switch error { 138 | case DecodingError.typeMismatch(_, let context): 139 | self.errorMessage = "\(error.localizedDescription): \(context.debugDescription)" 140 | case DecodingError.valueNotFound(_, let context): 141 | self.errorMessage = "\(error.localizedDescription): \(context.debugDescription)" 142 | case DecodingError.keyNotFound(_, let context): 143 | self.errorMessage = "\(error.localizedDescription): \(context.debugDescription)" 144 | case DecodingError.dataCorrupted(let key): 145 | self.errorMessage = "\(error.localizedDescription): \(key)" 146 | default: 147 | self.errorMessage = "Error decoding document: \(error.localizedDescription)" 148 | } 149 | } 150 | } 151 | } 152 | 153 | fileprivate func updateBook() { 154 | if let id = book.id { 155 | let docRef = db.collection("books").document(id) 156 | do { 157 | try docRef.setData(from: book) 158 | } 159 | catch { 160 | print(error) 161 | } 162 | } 163 | } 164 | 165 | fileprivate func addBook() { 166 | let collectionRef = db.collection("books") 167 | do { 168 | let newDocReference = try collectionRef.addDocument(from: self.book) 169 | print("Book stored with new document reference: \(newDocReference)") 170 | } 171 | catch { 172 | print(error) 173 | } 174 | } 175 | } 176 | 177 | struct MappingSimpleTypesScreen: View { 178 | @StateObject var viewModel = MappingSimpleTypesViewModel() 179 | 180 | @ViewBuilder 181 | var messageView: some View { 182 | if let errorMessage = viewModel.errorMessage { 183 | Text(errorMessage).foregroundColor(.red) 184 | } 185 | else { 186 | EmptyView() 187 | } 188 | } 189 | 190 | var body: some View { 191 | SampleScreen("Mapping Simple Types", introduction: "Firestore makes mapping simple data types as simple as possible.") { 192 | Form { 193 | Section(header: Text("Book"), footer: messageView) { 194 | TextField("Title", text: $viewModel.book.title) 195 | TextField("Number of pages", value: $viewModel.book.numberOfPages, formatter: NumberFormatter()) 196 | TextField("Author", text: $viewModel.book.author) 197 | } 198 | Section(header: Text("Actions")) { 199 | Button(action: viewModel.fetchAndMap) { 200 | Label("Fetch and map a book", systemImage: "square.and.arrow.up") 201 | } 202 | Button(action: viewModel.fetchAndMapNonExisting) { 203 | Label("Fetch and map a non-existing book", systemImage: "square.and.arrow.up") 204 | } 205 | Button(action: viewModel.fetchAndTryMappingInvalidData) { 206 | Label("Try mapping book from invalid data", systemImage: "square.and.arrow.up") 207 | } 208 | 209 | Button(action: viewModel.updateBook) { 210 | Label("Udpate book", systemImage: "square.and.arrow.down") 211 | } 212 | Button(action: viewModel.addBook) { 213 | Label("Add Book", systemImage: "plus") 214 | } 215 | } 216 | } 217 | } 218 | } 219 | } 220 | 221 | struct MappingSimpleTypesScreen_Previews: PreviewProvider { 222 | static var previews: some View { 223 | NavigationView { 224 | MappingSimpleTypesScreen() 225 | } 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /codable/FirestoreCodableSamples/Screens/MenuScreen.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MenuScreen.swift 3 | // FirestoreCodableSamples 4 | // 5 | // Created by Peter Friese on 17.03.21. 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | import SwiftUI 20 | 21 | struct MenuScreen: View { 22 | var body: some View { 23 | SampleScreen("Firestore & Codable", 24 | introduction: "Learn how to map data from Firestore using Swift's Codable API. \n\n" 25 | + "To run this demo, install the Firebase Emulator Suite and run the 'start.sh' script in the root folder of this project.") 26 | { 27 | Form { 28 | Section(header: Text("Mapping manually")) { 29 | NavigationLink(destination: ManuallyMappingSimpleTypesScreen()) { 30 | Label("Manually mapping simple types", systemImage: "hand.draw") 31 | } 32 | } 33 | Section(header: Text("Mapping simple types")) { 34 | NavigationLink(destination: MappingSimpleTypesScreen()) { 35 | Label("Mapping simple types", systemImage: "list.bullet.rectangle") 36 | } 37 | } 38 | Section(header: Text("Mapping complex types")) { 39 | NavigationLink(destination: MappingCustomTypesScreen()) { 40 | Label("Mapping custom types", systemImage: "rectangle.3.offgrid") 41 | } 42 | NavigationLink(destination: MappingArraysScreen()) { 43 | Label("Mapping arrays", systemImage: "square.stack.3d.down.forward") 44 | } 45 | NavigationLink(destination: MappingArraysWithNestedTypesScreen()) { 46 | Label("Mapping arrays w/ custom types", systemImage: "square.stack.3d.down.forward") 47 | } 48 | NavigationLink(destination: CustomizeMappingScreen()) { 49 | Label("Mapping dates and times", systemImage: "calendar.badge.clock") 50 | } 51 | NavigationLink(destination: MappingGeoPointsScreen()) { 52 | Label("Mapping GeoPoints", systemImage: "globe") 53 | } 54 | NavigationLink(destination: MappingColorsScreen()) { 55 | Label("Mapping Colors", systemImage: "paintpalette") 56 | } 57 | NavigationLink(destination: MappingEnumsScreen()) { 58 | Label("Mapping Enums", systemImage: "list.number") 59 | } 60 | NavigationLink(destination: CustomizeMappingScreen()) { 61 | Label("Custom mapping", systemImage: "sparkles.square.fill.on.square") 62 | } 63 | } 64 | } 65 | } 66 | } 67 | } 68 | 69 | struct MenuScreen_Previews: PreviewProvider { 70 | static var previews: some View { 71 | NavigationView { 72 | MenuScreen() 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /codable/FirestoreCodableSamples/Utilities/Color+Codable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Color+Codable.swift 3 | // FirestoreCodableSamples 4 | // 5 | // Created by Peter Friese on 18.03.21. 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | import SwiftUI 20 | 21 | // Inspired by https://cocoacasts.com/from-hex-to-uicolor-and-back-in-swift 22 | // Make Color codable. This includes support for transparency. 23 | // See https://www.digitalocean.com/community/tutorials/css-hex-code-colors-alpha-values 24 | extension Color: Codable { 25 | init(hex: String) { 26 | let rgba = hex.toRGBA() 27 | 28 | self.init(.sRGB, 29 | red: Double(rgba.r), 30 | green: Double(rgba.g), 31 | blue: Double(rgba.b), 32 | opacity: Double(rgba.alpha)) 33 | } 34 | 35 | public init(from decoder: Decoder) throws { 36 | let container = try decoder.singleValueContainer() 37 | let hex = try container.decode(String.self) 38 | 39 | let rgba = hex.toRGBA() 40 | 41 | self.init(.sRGB, 42 | red: Double(rgba.r), 43 | green: Double(rgba.g), 44 | blue: Double(rgba.b), 45 | opacity: Double(rgba.alpha)) 46 | } 47 | 48 | public func encode(to encoder: Encoder) throws { 49 | var container = encoder.singleValueContainer() 50 | try container.encode(toHex) 51 | } 52 | 53 | var toHex: String? { 54 | return toHex() 55 | } 56 | 57 | func toHex(alpha: Bool = false) -> String? { 58 | guard let components = cgColor?.components, components.count >= 3 else { 59 | return nil 60 | } 61 | 62 | let r = Float(components[0]) 63 | let g = Float(components[1]) 64 | let b = Float(components[2]) 65 | var a = Float(1.0) 66 | 67 | if components.count >= 4 { 68 | a = Float(components[3]) 69 | } 70 | 71 | if alpha { 72 | return String(format: "#%02lX%02lX%02lX%02lX", 73 | lroundf(r * 255), 74 | lroundf(g * 255), 75 | lroundf(b * 255), 76 | lroundf(a * 255)).lowercased() 77 | } 78 | else { 79 | return String(format: "#%02lX%02lX%02lX", 80 | lroundf(r * 255), 81 | lroundf(g * 255), 82 | lroundf(b * 255)).lowercased() 83 | } 84 | } 85 | } 86 | 87 | extension String { 88 | func toRGBA() -> (r: CGFloat, g: CGFloat, b: CGFloat, alpha: CGFloat) { 89 | var hexSanitized = self.trimmingCharacters(in: .whitespacesAndNewlines) 90 | hexSanitized = hexSanitized.replacingOccurrences(of: "#", with: "") 91 | 92 | var rgb: UInt64 = 0 93 | 94 | var r: CGFloat = 0.0 95 | var g: CGFloat = 0.0 96 | var b: CGFloat = 0.0 97 | var a: CGFloat = 1.0 98 | 99 | let length = hexSanitized.count 100 | 101 | Scanner(string: hexSanitized).scanHexInt64(&rgb) 102 | 103 | if length == 6 { 104 | r = CGFloat((rgb & 0xFF0000) >> 16) / 255.0 105 | g = CGFloat((rgb & 0x00FF00) >> 8) / 255.0 106 | b = CGFloat(rgb & 0x0000FF) / 255.0 107 | } 108 | else if length == 8 { 109 | r = CGFloat((rgb & 0xFF000000) >> 24) / 255.0 110 | g = CGFloat((rgb & 0x00FF0000) >> 16) / 255.0 111 | b = CGFloat((rgb & 0x0000FF00) >> 8) / 255.0 112 | a = CGFloat(rgb & 0x000000FF) / 255.0 113 | } 114 | 115 | return (r, g, b, a) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /codable/FirestoreCodableSamples/Utilities/Color+FontColor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Color+FontColor.swift 3 | // FirestoreCodableSamples 4 | // 5 | // Created by Peter Friese on 19.03.21. 6 | // 7 | // This code is from Apple's ScrumDinger tutorial. 8 | // 9 | // Licensed under the Apache License, Version 2.0 (the "License"); 10 | // you may not use this file except in compliance with the License. 11 | // You may obtain a copy of the License at 12 | // 13 | // http://www.apache.org/licenses/LICENSE-2.0 14 | // 15 | // Unless required by applicable law or agreed to in writing, software 16 | // distributed under the License is distributed on an "AS IS" BASIS, 17 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | // See the License for the specific language governing permissions and 19 | // limitations under the License. 20 | 21 | import SwiftUI 22 | 23 | extension Color { 24 | // MARK: - font colors 25 | /// This color is either black or white, whichever is more accessible when viewed against the scrum color. 26 | var accessibleFontColor: Color { 27 | var red: CGFloat = 0 28 | var green: CGFloat = 0 29 | var blue: CGFloat = 0 30 | UIColor(self).getRed(&red, green: &green, blue: &blue, alpha: nil) 31 | return isLightColor(red: red, green: green, blue: blue) ? .black : .white 32 | } 33 | 34 | private func isLightColor(red: CGFloat, green: CGFloat, blue: CGFloat) -> Bool { 35 | let lightRed = red > 0.65 36 | let lightGreen = green > 0.65 37 | let lightBlue = blue > 0.65 38 | 39 | let lightness = [lightRed, lightGreen, lightBlue].reduce(0) { $1 ? $0 + 1 : $0 } 40 | return lightness >= 2 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /codable/FirestoreCodableSamples/Utilities/SampleScreen.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Introduction.swift 3 | // FirestoreCodableSamples 4 | // 5 | // Created by Peter Friese on 17.03.21. 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | import SwiftUI 20 | 21 | struct SampleScreen: View { 22 | var title: String 23 | var introduction: Text 24 | var content: () -> Content 25 | 26 | init(_ title: String, introduction: String, @ViewBuilder content: @escaping () -> Content) { 27 | self.title = title 28 | self.introduction = Text(introduction) 29 | self.content = content 30 | } 31 | 32 | init(_ title: String, _ introduction: Text, @ViewBuilder content: @escaping () -> Content) { 33 | self.title = title 34 | self.introduction = introduction 35 | self.content = content 36 | } 37 | 38 | var body: some View { 39 | VStack(alignment: .leading) { 40 | introduction 41 | .font(.subheadline) 42 | .foregroundColor(Color(UIColor.secondaryLabel)) 43 | .padding() 44 | content() 45 | } 46 | .navigationBarTitle(title) 47 | } 48 | } 49 | 50 | struct SampleScreen_Previews: PreviewProvider { 51 | static let multiLineIntro = 52 | """ 53 | This is a multiline string 54 | Just checking out 55 | Third line. 56 | """ 57 | static var previews: some View { 58 | NavigationView { 59 | SampleScreen("Demo", 60 | introduction: multiLineIntro 61 | ) { 62 | List { 63 | Text("Sample") 64 | } 65 | } 66 | } 67 | .previewLayout(.sizeThatFits) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /codable/data/auth_export/accounts.json: -------------------------------------------------------------------------------- 1 | {"kind":"identitytoolkit#DownloadAccountResponse","users":[{"localId":"CLKg2yRKdnjbY2gcQW0xNbuHJsnp","createdAt":"1652964087522","lastLoginAt":"1652964087521","validSince":"1665389798","emailVerified":false,"disabled":false},{"localId":"WITZGC3XiSQSEr1BdTPUzPkTs9Ap","createdAt":"1652773048681","lastLoginAt":"1652773048680","validSince":"1665389798","emailVerified":false,"disabled":false},{"localId":"j8F8jxfiYaPlrrut8O71O3mvsyAy","lastLoginAt":"1665390083858","createdAt":"1665390083858","lastRefreshAt":"2022-10-10T08:21:23.859Z"}]} -------------------------------------------------------------------------------- /codable/data/auth_export/config.json: -------------------------------------------------------------------------------- 1 | {"signIn":{"allowDuplicateEmails":false}} -------------------------------------------------------------------------------- /codable/data/firebase-export-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "11.11.0", 3 | "firestore": { 4 | "version": "1.14.4", 5 | "path": "firestore_export", 6 | "metadata_file": "firestore_export/firestore_export.overall_export_metadata" 7 | }, 8 | "auth": { 9 | "version": "11.11.0", 10 | "path": "auth_export" 11 | } 12 | } -------------------------------------------------------------------------------- /codable/data/firestore_export/all_namespaces/all_kinds/all_namespaces_all_kinds.export_metadata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterfriese/Swift-Firestore-Guide/5c844b9a1665112174fd6e2839740bf04871748b/codable/data/firestore_export/all_namespaces/all_kinds/all_namespaces_all_kinds.export_metadata -------------------------------------------------------------------------------- /codable/data/firestore_export/all_namespaces/all_kinds/output-0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterfriese/Swift-Firestore-Guide/5c844b9a1665112174fd6e2839740bf04871748b/codable/data/firestore_export/all_namespaces/all_kinds/output-0 -------------------------------------------------------------------------------- /codable/data/firestore_export/firestore_export.overall_export_metadata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterfriese/Swift-Firestore-Guide/5c844b9a1665112174fd6e2839740bf04871748b/codable/data/firestore_export/firestore_export.overall_export_metadata -------------------------------------------------------------------------------- /codable/firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "emulators": { 3 | "firestore": { 4 | "port": 8080, 5 | "rules": "firestore.rules" 6 | }, 7 | "ui": { 8 | "enabled": true, 9 | "-port": 4000 10 | }, 11 | "auth": { 12 | "port": 9099 13 | } 14 | }, 15 | "firestore": { 16 | "rules": "firestore.rules", 17 | "indexes": "firestore.indexes.json" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /codable/firestore.indexes.json: -------------------------------------------------------------------------------- 1 | { 2 | "indexes": [], 3 | "fieldOverrides": [] 4 | } 5 | -------------------------------------------------------------------------------- /codable/firestore.rules: -------------------------------------------------------------------------------- 1 | rules_version = '2'; 2 | service cloud.firestore { 3 | match /databases/{database}/documents { 4 | match /{document=**} { 5 | allow read, write: if request.auth != null; 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /codable/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | firebase emulators:start --import=./data --export-on-exit 3 | -------------------------------------------------------------------------------- /images/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterfriese/Swift-Firestore-Guide/5c844b9a1665112174fd6e2839740bf04871748b/images/header.png -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterfriese/Swift-Firestore-Guide/5c844b9a1665112174fd6e2839740bf04871748b/images/logo.png -------------------------------------------------------------------------------- /images/swift-logo-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterfriese/Swift-Firestore-Guide/5c844b9a1665112174fd6e2839740bf04871748b/images/swift-logo-512.png -------------------------------------------------------------------------------- /images/swift-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterfriese/Swift-Firestore-Guide/5c844b9a1665112174fd6e2839740bf04871748b/images/swift-logo.png --------------------------------------------------------------------------------