├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Podfile ├── Podfile.lock ├── README.md ├── twitchConferences.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── wizage.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist └── twitchConferences ├── AppDelegate.swift ├── Assets.xcassets ├── AppIcon.appiconset │ └── Contents.json └── Contents.json ├── Backend ├── DetailTalkStore.swift └── TalkStore.swift ├── Base.lproj └── LaunchScreen.storyboard ├── Info.plist ├── Preview Content └── Preview Assets.xcassets │ └── Contents.json ├── SceneDelegate.swift └── Views ├── AddTalkView.swift ├── ContentView.swift └── DetailView.swift /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check [existing open](https://github.com/aws-samples/aws-amplify-swiftui-demo/issues), or [recently closed](https://github.com/aws-samples/aws-amplify-swiftui-demo/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/aws-samples/aws-amplify-swiftui-demo/labels/help%20wanted) issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](https://github.com/aws-samples/aws-amplify-swiftui-demo/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | target 'twitchConferences' do 5 | # Comment the next line if you don't want to use dynamic frameworks 6 | use_frameworks! 7 | 8 | # Pods for twitchConferences 9 | pod 'AWSAppSync' 10 | pod 'AWSPinpoint' 11 | pod 'AWSMobileClient' 12 | 13 | end 14 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - AWSAppSync (2.14.0): 3 | - AWSCore (~> 2.9.0) 4 | - ReachabilitySwift (= 4.3.0) 5 | - SQLite.swift (= 0.11.6) 6 | - AWSAuthCore (2.9.10): 7 | - AWSCore (= 2.9.10) 8 | - AWSCognitoIdentityProvider (2.9.10): 9 | - AWSCognitoIdentityProviderASF (= 1.0.1) 10 | - AWSCore (= 2.9.10) 11 | - AWSCognitoIdentityProviderASF (1.0.1) 12 | - AWSCore (2.9.10) 13 | - AWSMobileClient (2.9.10): 14 | - AWSAuthCore (= 2.9.10) 15 | - AWSCognitoIdentityProvider (= 2.9.10) 16 | - AWSPinpoint (2.9.10): 17 | - AWSCore (= 2.9.10) 18 | - ReachabilitySwift (4.3.0) 19 | - SQLite.swift (0.11.6): 20 | - SQLite.swift/standard (= 0.11.6) 21 | - SQLite.swift/standard (0.11.6) 22 | 23 | DEPENDENCIES: 24 | - AWSAppSync 25 | - AWSMobileClient 26 | - AWSPinpoint 27 | 28 | SPEC REPOS: 29 | https://github.com/cocoapods/specs.git: 30 | - AWSAppSync 31 | - AWSAuthCore 32 | - AWSCognitoIdentityProvider 33 | - AWSCognitoIdentityProviderASF 34 | - AWSCore 35 | - AWSMobileClient 36 | - AWSPinpoint 37 | - ReachabilitySwift 38 | - SQLite.swift 39 | 40 | SPEC CHECKSUMS: 41 | AWSAppSync: 91f63b95f572d908f0a001d5c3016b117e49297a 42 | AWSAuthCore: d6d222a7eeae403f968afb5b9b3975bd96340d91 43 | AWSCognitoIdentityProvider: 5e72467f174a970e9d357fc06666e3f32e6eda70 44 | AWSCognitoIdentityProviderASF: f94f1a502e72ef3d0a1de93e10bf7a79c8698118 45 | AWSCore: 22b6bbc6761ca9981d878155d11bc02edd681a64 46 | AWSMobileClient: a0768be5b34a5a2eb39fd3fcd5d4f14125d4ebbb 47 | AWSPinpoint: 768a9ed368fef4424fd598fc75379c96ad9df631 48 | ReachabilitySwift: 408477d1b6ed9779dba301953171e017c31241f3 49 | SQLite.swift: 46d890be8601964454bd3392527f863d1b802d45 50 | 51 | PODFILE CHECKSUM: 501fa34e2dcbb8b6d79399a2c236796f82beaee4 52 | 53 | COCOAPODS: 1.7.1 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Amplify SwiftUI Demo 2 | 3 | Get started using Amplify and SwiftUI 4 | 5 | ## Getting Started - Clone the repo 6 | 7 | First we will be getting started with the master branch so go head and clone it into a working directory. 8 | 9 | ### Configuring the iOS applicaion - AppSync iOS Client SDK 10 | 11 | Install the AppSync iOS SDK by running: 12 | ```js 13 | pod install --repo-update 14 | ``` 15 | 16 | ### Install and Configure the Amplify CLI - Just Once 17 | 18 | Next, we'll install the AWS Amplify CLI: 19 | 20 | ```bash 21 | npm install -g @aws-amplify/cli 22 | ``` 23 | 24 | After installation, configure the CLI with your developer credentials: 25 | 26 | Note: If you already have the AWS CLI installed and use a named profile, you can skip the `amplify configure` step. 27 | `Amplify configure` is going to have you launch the AWS Management Console, create a new IAM User, asign an IAM Policy, and collect the programmatic credentials to craate a CLI profile that will be used to provision AWS resources for each project in future steps. 28 | 29 | ```js 30 | amplify configure 31 | ``` 32 | 33 | > If you'd like to see a video walkthrough of this configuration process, click [here](https://www.youtube.com/watch?v=fWbM5DLh25U). 34 | 35 | Here we'll walk through the `amplify configure` setup. Once you've signed in to the AWS console, continue: 36 | - Specify the AWS Region: __us-east-1__ 37 | - Specify the username of the new IAM user: __amplify-workshop-user__ 38 | > In the AWS Console, click __Next: Permissions__, __Next: Tags__, __Next: Review__, & __Create User__ to create the new IAM user. Then, return to the command line & press Enter. 39 | - Enter the access key of the newly created user: 40 | ? accessKeyId: __()__ 41 | ? secretAccessKey: __()__ 42 | - Profile Name: __amplify-workshop-user__ 43 | 44 | ### Initializing A New Amplify Project 45 | From the root of your Xcode project folder: 46 | ```bash 47 | amplify init 48 | ``` 49 | 50 | - Enter a name for the project: __iosamplifyapp__ 51 | - Enter a name for the environment: __master__ 52 | - Choose your default editor: __Visual Studio Code (or your default editor)__ 53 | - Please choose the type of app that you're building __ios__ 54 | - Do you want to use an AWS profile? __Y__ 55 | - Please choose the profile you want to use: __amplify-workshop-user__ 56 | 57 | AWS Amplify CLI will iniatilize a new project & you'll see a new folder: __amplify__ & a new file called `awsconfiguration.json` in the root directory. These files hold your Amplify project configuration. 58 | 59 | To view the status of the amplify project at any time, you can run the Amplify `status` command: 60 | 61 | ```sh 62 | amplify status 63 | ``` 64 | 65 | ## Adding a GraphQL API 66 | In this section we'll add a new GraphQL API via AWS AppSync to our iOS project backend. 67 | To add a GraphQL API, we can use the following command: 68 | 69 | ```sh 70 | amplify add api 71 | ``` 72 | 73 | Answer the following questions: 74 | 75 | - Please select from one of the above mentioned services __GraphQL__ 76 | - Provide API name: __ConferenceAPI__ 77 | - Choose an authorization type for the API __API key__ 78 | - Do you have an annotated GraphQL schema? __N__ 79 | - Do you want a guided schema creation? __Y__ 80 | - What best describes your project: __Single object with fields (e.g. “Todo” with ID, name, description)__ 81 | - Do you want to edit the schema now? (Y/n) __Y__ 82 | 83 | When prompted and the default schema launches in your favorite editor, update the default schema to the following: 84 | 85 | ```graphql 86 | type Talk @model { 87 | id: ID! 88 | clientId: ID 89 | name: String! 90 | description: String! 91 | speakerName: String! 92 | speakerBio: String! 93 | } 94 | ``` 95 | 96 | Next, let's deploy the GraphQL API into our account: 97 | This step take the local CloudFormation templates and deployes them to the AWS Cloud for provisioning of the services you enabled via the `add API` category. 98 | ```bash 99 | amplify push 100 | ``` 101 | 102 | - Do you want to generate code for your newly created GraphQL API __Y__ 103 | - Enter the file name pattern of graphql queries, mutations and subscriptions: __(graphql/**/*.graphql)__ 104 | - Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions? __Y__ 105 | - Enter maximum statement depth [increase from default if your schema is deeply nested] __2__ 106 | - Enter the file name for the generated code __API.swift__ 107 | 108 | > To view the new AWS AppSync API at any time after its creation, go to the dashboard at [https://console.aws.amazon.com/appsync](https://console.aws.amazon.com/appsync). Also be sure that your region is set correctly. 109 | 110 | ## Add the `awsconfiguration.json` and `API.swift` files to your Xcode project 111 | 112 | We need to configure our iOS Swift application to be aware of our new AWS Amplify project. We do this by referencing the auto-generated `awsconfiguration.json` and `API.Swift` files in the root of your Xcode project folder. 113 | 114 | Launch Xcode using the .xcworkspace from now on as we are using Cocoapods. 115 | ```bash 116 | $ open twitchConferences.xcworkspace/ 117 | ``` 118 | 119 | In Xcode, right-click on the project folder and choose `"Add Files to ..."` and add the `awsconfiguration.json` and the `API.Swift` files to your project. When the Options dialog box that appears, do the following: 120 | 121 | * Clear the Copy items if needed check box. 122 | * Choose Create groups, and then choose Next. 123 | 124 | Build the project (Command-B) to make sure we don't have any compile errors. 125 | 126 | ## Initialize the AppSync Store and drawing the list view 127 | 128 | ### AppDelegate.swift 129 | 130 | Add these lines to your AppDelegate file, to configure your AppSync Client. 131 | 132 | ```swift 133 | import UIKit 134 | import AWSAppSync 135 | ... 136 | @UIApplicationMain 137 | class AppDelegate: UIResponder, UIApplicationDelegate { 138 | public var appSyncClient: AWSAppSyncClient! 139 | ... 140 | 141 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 142 | do { 143 | let appSyncConfig = try AWSAppSyncClientConfiguration(appSyncServiceConfig: AWSAppSyncServiceConfig(),cacheConfiguration: AWSAppSyncCacheConfiguration()) 144 | 145 | appSyncClient = try AWSAppSyncClient(appSyncConfig: appSyncConfig) 146 | 147 | // Initialize the AWS AppSync client 148 | 149 | } catch { 150 | print("Error initializing AppSync client. \(error)") 151 | appSyncClient = nil 152 | } 153 | return true 154 | } 155 | ``` 156 | 157 | ### TalkStore.swift 158 | 159 | Now that we have a configure AppSync Client we need to configure our TalkStore. This TalkStore contains the data that our views will be referencing throughout our design. With the release of the new SwiftUI and Combine framework they introducted a few new object types. The one we are going to be using today for our store is a `BindableObject`. 160 | 161 | To get started with `TalkStore` first make the it inherit the `BindableObject` class and also add AWSAppSync to our imports. 162 | ```swift 163 | import AWSAppSync 164 | 165 | final class TalkStore: BindableObject { 166 | ``` 167 | 168 | Now to make it adhere to the `BindableObject` class we need to add a PublisherType so whenever anything changes we will publish to a topic notifying the view that things have changed and it should redraw. For those that are familiar with React this is similiar to how props work. 169 | 170 | ```swift 171 | /* 172 | Required by SwiftUI 173 | */ 174 | let didChange = PassthroughSubject() 175 | var listTalks: [ListTalksQuery.Data.ListTalk.Item] { 176 | didSet { 177 | didChange.send(self) 178 | } 179 | 180 | } 181 | 182 | //We will be using this later. 183 | private let appSyncClient: AWSAppSyncClient! 184 | ``` 185 | 186 | Now lets create the init. We will need to set the initial value for the list of talks and also setup the AppSync Client for this store to be using the client we configure earlier in the AppDelegate. 187 | 188 | ```swift 189 | init(){ 190 | self.listTalks = [] 191 | appSyncClient = (UIApplication.shared.delegate as! AppDelegate).appSyncClient 192 | 193 | // Initialize the AWS AppSync client 194 | appSyncClient.fetch(query: ListTalksQuery(), cachePolicy: .returnCacheDataAndFetch) { (result, error) in 195 | if (error != nil){ 196 | print(error?.localizedDescription ?? "") 197 | return 198 | } else { 199 | guard let talks = result?.data?.listTalks?.items else { return } 200 | self.listTalks = talks as! [ListTalksQuery.Data.ListTalk.Item] 201 | } 202 | } 203 | } 204 | ``` 205 | 206 | That is all we need to do right now in this file. We will be coming back soon to add some unit tests for the UIView but for now we will move on. 207 | 208 | 209 | ### ContentView.swift 210 | 211 | Click resume on the top of the Canvas on the right to make sure the project is not seeing any errors before diving in. 212 | 213 | First lets add some fields that should show in the list view by using the default view. This can be formatted anyway but should contain a place for the `name` and the `speakerName` to a cell. Here is an example: 214 | 215 | ```swift 216 | struct ContentView : View { 217 | var body: some View { 218 | VStack(alignment: .leading) { 219 | Text("name") 220 | .font(.title) 221 | Text("speakerName") 222 | .font(.subheadline) 223 | } 224 | } 225 | } 226 | ``` 227 | 228 | Now that the cell has been defined, the next step is defining the list. First it is important to bring in the store needed to populate the `Table`. In your `ContentView` add in a `@State var` for the store. Something like this: 229 | ```swift 230 | struct ContentView : View { 231 | @EnvironmentObject var talkStore : TalkStore 232 | 233 | ... 234 | 235 | } 236 | ``` 237 | 238 | You will now notice that we have errors if you try to run the app or use the resume button. This is because the enviroment object we just added needs to be initalized for both the testing and the production level builds. 239 | 240 | To add the production fix: 241 | Navigate to `SceneDelgate.swift` and find this line: 242 | ```swift 243 | window.rootViewController = UIHostingController(rootView: ContentView()) 244 | ``` 245 | 246 | `ContentView` will need an `environmentObject` to be able to function correctly. 247 | 248 | To add an `enviromentOjbect` to `ContentView` replace this line with this. 249 | 250 | ```swift 251 | window.rootViewController = UIHostingController(rootView: ContentView().environmentObject(TalkStore())) 252 | ``` 253 | 254 | To add the test fix: 255 | 256 | Navigate back to the `TalkStore.swift` and add in this new `init()` for testing: 257 | ```swift 258 | /* 259 | Init if running app is using SwiftUI Content View 260 | */ 261 | init(talks: [ListTalksQuery.Data.ListTalk.Item]){ 262 | self.appSyncClient = nil 263 | self.listTalks = talks 264 | } 265 | ``` 266 | Now to get our resume fixed we need to add some test data in for our init: 267 | Go back to the `ContentView.swift` and scroll down to the `#if DEBUG` and add some test data like so: 268 | ```swift 269 | #if DEBUG 270 | struct ContentView_Previews : PreviewProvider { 271 | static var previews: some View { 272 | let sampleData = [ 273 | ListTalksQuery.Data.ListTalk.Item(id: "0", name: "SwiftUI and Amplify", description: "", speakerName: "Sam Patzer", speakerBio: ""), 274 | ListTalksQuery.Data.ListTalk.Item(id: "1", name: "WWDC Recap", description: "", speakerName: "Tim Apple", speakerBio: ""), 275 | ListTalksQuery.Data.ListTalk.Item(id: "2", name: "Bash Party", description: "", speakerName: "Weezer", speakerBio: "") 276 | ] 277 | return ContentView() 278 | .environmentObject(TalkStore(talks: sampleData)) 279 | } 280 | } 281 | #endif 282 | ``` 283 | 284 | Now we have some canvas data available so we want to build a list view that actually shows that data instead of the name and speaker place holders. 285 | 286 | Lets first add our data to our `VStack` through List like so: 287 | ```swift 288 | var body: some View { 289 | List(talkStore.listTalks.identified(by:\.id)){ talk in 290 | VStack(alignment: .leading) { 291 | ... 292 | } 293 | } 294 | .listStyle(.grouped) 295 | } 296 | ``` 297 | It is important to note the `.identified(by: )` section. SwiftUI requires each row to have a unique id associated with it. 298 | 299 | Now to actually get the data out of the talk variable you just need to reference it like any other string in swift: 300 | ```swift 301 | "\(talk.name) 302 | ``` 303 | 304 | Now we have successfully created a list view using SwiftUI and AppSync. Up next we need to add data to our table! 305 | 306 | ## Adding data to the table and creating talks 307 | 308 | ### TalkStore.swift 309 | 310 | First to add talks we need to add the ability to add talks to through our Store which communicates with our AppSync endpoint. 311 | 312 | ```swift 313 | func add(create: CreateTalkInput){ 314 | if (appSyncClient != nil){ 315 | print("Appsync not null") 316 | appSyncClient?.perform(mutation: CreateTalkMutation(input: create)) 317 | { (result, error) in 318 | print("Calling") 319 | if let error = error as? AWSAppSyncClientError { 320 | print("Error occurred: \(error.localizedDescription )") 321 | return 322 | } 323 | if let resultError = result?.errors { 324 | print("Error saving conf talk: \(resultError)") 325 | return 326 | } 327 | 328 | guard let result = result?.data else { return } 329 | self.listTalks.append(self.mapAdd(neededConversion: result.createTalk!)) 330 | print("Talk created: \(String(describing: result.createTalk?.id))") 331 | } 332 | //write it to our backend 333 | } else { 334 | listTalks.append(mapAdd(neededConversion: CreateTalkMutation.Data.CreateTalk(id: "0", name: create.name, description: create.description, speakerName: create.speakerName, speakerBio: create.speakerBio))) 335 | } 336 | } 337 | 338 | private func mapAdd(neededConversion: 339 | CreateTalkMutation.Data.CreateTalk) -> ListTalksQuery.Data.ListTalk.Item{ 340 | let newItem = ListTalksQuery.Data.ListTalk.Item(id: neededConversion.id, clientId: neededConversion.clientId, name: neededConversion.name, description: neededConversion.description, speakerName: neededConversion.speakerName, speakerBio: neededConversion.speakerBio) 341 | return newItem 342 | } 343 | ``` 344 | 345 | You will notice that if the appSyncClient is nil then we will just append it to the array and move on. This is for testing purposes only. 346 | 347 | ### AddTalkView.swift 348 | 349 | Now we need to crate our form for creating our talks! 350 | 351 | First lets create our Form: 352 | 353 | ```swift 354 | 355 | enum StateOfCreation { 356 | case save 357 | case dismiss 358 | case hide 359 | case show 360 | } 361 | 362 | struct AddTalk : View { 363 | @Binding var talk : CreateTalkInput 364 | @Binding var isShowing : StateOfCreation 365 | @EnvironmentObject var talkStore : TalkStore 366 | 367 | var body: some View { 368 | NavigationView{ 369 | Form{ 370 | Section(header:Text("Talk Name")){ 371 | TextField($talk.name) 372 | .lineLimit(1) 373 | } 374 | Section(header:Text("Talk Description")){ 375 | TextField($talk.description) 376 | .frame(height:150) 377 | } 378 | Section(header:Text("Speaker Name")){ 379 | TextField($talk.speakerName) 380 | .lineLimit(1) 381 | } 382 | Section(header:Text("Speaker Bio")){ 383 | TextField($talk.speakerBio) 384 | .frame(height:150) 385 | } 386 | } 387 | .listStyle(.grouped) 388 | .navigationBarTitle(Text("Add Talk")) 389 | .navigationBarItems(trailing: 390 | Button(action: { 391 | if (self.talk.name == "" || self.talk.speakerName == ""){ 392 | 393 | } else { 394 | self.isShowing = .save 395 | self.talkStore.add(create:self.talk) 396 | } 397 | 398 | }, label: { 399 | Text("Save") 400 | })) 401 | } 402 | } 403 | } 404 | ``` 405 | 406 | To view what this looks like in the live view we need to modify the test like below: 407 | 408 | ```swift 409 | #if DEBUG 410 | struct AddTalk_Previews : PreviewProvider { 411 | static var previews: some View { 412 | return AddTalk(talk: .constant(.init(name: "", description: "", speakerName: "", speakerBio: "")), isShowing: .constant(.show)) 413 | } 414 | } 415 | #endif 416 | ``` 417 | 418 | ### ContentView.swift 419 | 420 | Now that we have a great looking Form we need to add the ability to actually see this form from our list! 421 | 422 | Surround the `List{}` with a ``NavigationView` and add in two new state variables that will help us keep track of if we are displaying the form and what the value of the talk is. It should look something like this: 423 | ```swift 424 | struct ContentView : View { 425 | @EnvironmentObject var talkStore : TalkStore 426 | @State var shouldCreate : StateOfCreation = .hide 427 | @State var newTalk : CreateTalkInput = CreateTalkInput(name: "", description: "", speakerName: "", speakerBio: "") 428 | var body: some View { 429 | NavigationView { 430 | List { 431 | ... 432 | } 433 | } 434 | } 435 | } 436 | 437 | ``` 438 | 439 | Now we can navigate since we added a NavigationView! Now we just need add a function that will actually present the view. 440 | 441 | After the `.listStyle` we will be adding some Navigation Bar items. This first item will be a title and the second is a plus button to show our form we made! 442 | 443 | ```swift 444 | .listStyle(.grouped) 445 | .navigationBarTitle(Text("Talks")) 446 | .navigationBarItems(trailing: Button(action: { 447 | self.shouldCreate = .show 448 | }, label: { 449 | Image(systemName: "plus").font(.title) 450 | }).disabled(self.shouldCreate == .show)) 451 | .presentation(self.shouldCreate == .show ? Modal(AddTalk(talk: $newTalk, isShowing:$shouldCreate).environmentObject(talkStore), onDismiss: { 452 | self.shouldCreate = .hide 453 | self.newTalk = CreateTalkInput(name: "", description: "", speakerName: "", speakerBio: "") 454 | }) : nil) 455 | ``` 456 | 457 | And there we go! Now we can add talks to our list of talks. 458 | 459 | ## Analytics 460 | 461 | ### Back into the terminal 462 | 463 | First lets us go back to our terminal and add some the backend to create some Analytics. 464 | 465 | Run this command: 466 | 467 | ```bash 468 | amplify add analytics 469 | ``` 470 | 471 | Using service: Pinpoint, provided by: awscloudformation 472 | - Provide your pinpoint resource name: __iosamplifyapp__ 473 | - Apps need authorization to send analytics events. Do you want to allow guests and unauthenticated users to send anal 474 | ytics events? (we recommend you allow this when getting started) __Yes__ 475 | 476 | Then run 477 | ```bash 478 | amplify push 479 | ``` 480 | 481 | And confirm to add this to your backend. 482 | 483 | Now you are adding some basic analytics to your application. 484 | 485 | Open your podfile and add this line under the AppSync dependancy. 486 | 487 | ``` 488 | pod 'AWSPinpoint' 489 | pod 'AWSMobileClient' 490 | ``` 491 | 492 | and run 493 | 494 | ```bash 495 | pod install 496 | ``` 497 | 498 | ### Back into Xcode 499 | 500 | ### AppDelegate.swift 501 | 502 | Add these lines to your AppDelegate file, to configure your AppSync Client. 503 | 504 | ```swift 505 | import AWSAppSync 506 | import AWSPinpoint 507 | import AWSMobileClient 508 | ... 509 | @UIApplicationMain 510 | class AppDelegate: UIResponder, UIApplicationDelegate { 511 | public var appSyncClient: AWSAppSyncClient! 512 | public var pinpoint: AWSPinpoint? 513 | ... 514 | 515 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 516 | ... 517 | AWSMobileClient.sharedInstance().initialize { (userState, error) in 518 | if let error = error { 519 | print("Error initializing AWSMobileClient: \(error.localizedDescription)") 520 | } else if let userState = userState { 521 | print("AWSMobileClient initialized. Current UserState: \(userState.rawValue)") 522 | } 523 | } 524 | 525 | // Initialize Pinpoint 526 | let pinpointConfiguration = AWSPinpointConfiguration.defaultPinpointConfiguration(launchOptions: launchOptions) 527 | pinpoint = AWSPinpoint(configuration: pinpointConfiguration) 528 | return true 529 | } 530 | ``` 531 | 532 | Now we are rocking and a rolling with Analytics! 533 | 534 | 535 | ## License Summary 536 | 537 | This sample code is made available under the MIT-0 license. See the LICENSE file. 538 | -------------------------------------------------------------------------------- /twitchConferences.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 51; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1BEA77BD22E6046A00F1EBE0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BEA77BC22E6046A00F1EBE0 /* AppDelegate.swift */; }; 11 | 1BEA77BF22E6046A00F1EBE0 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BEA77BE22E6046A00F1EBE0 /* SceneDelegate.swift */; }; 12 | 1BEA77C122E6046A00F1EBE0 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BEA77C022E6046A00F1EBE0 /* ContentView.swift */; }; 13 | 1BEA77C322E6046C00F1EBE0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1BEA77C222E6046C00F1EBE0 /* Assets.xcassets */; }; 14 | 1BEA77C622E6046C00F1EBE0 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1BEA77C522E6046C00F1EBE0 /* Preview Assets.xcassets */; }; 15 | 1BEA77C922E6046C00F1EBE0 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1BEA77C722E6046C00F1EBE0 /* LaunchScreen.storyboard */; }; 16 | 8D10FA4395855CBB0F920272 /* Pods_twitchConferences.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 94EC097AA12B05BAF81B7543 /* Pods_twitchConferences.framework */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXFileReference section */ 20 | 176450717F6AF4D925F71864 /* Pods-twitchConferences.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-twitchConferences.release.xcconfig"; path = "Target Support Files/Pods-twitchConferences/Pods-twitchConferences.release.xcconfig"; sourceTree = ""; }; 21 | 1BEA77B922E6046A00F1EBE0 /* twitchConferences.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = twitchConferences.app; sourceTree = BUILT_PRODUCTS_DIR; }; 22 | 1BEA77BC22E6046A00F1EBE0 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 23 | 1BEA77BE22E6046A00F1EBE0 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 24 | 1BEA77C022E6046A00F1EBE0 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 25 | 1BEA77C222E6046C00F1EBE0 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 26 | 1BEA77C522E6046C00F1EBE0 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 27 | 1BEA77C822E6046C00F1EBE0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 28 | 1BEA77CA22E6046C00F1EBE0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 29 | 1BEA77D622E6067200F1EBE0 /* AddTalkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddTalkView.swift; sourceTree = ""; }; 30 | 1BEA77D722E6067200F1EBE0 /* DetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailView.swift; sourceTree = ""; }; 31 | 1BEA77DE22E60E0600F1EBE0 /* TalkStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TalkStore.swift; sourceTree = ""; }; 32 | 1BEA77DF22E60E0600F1EBE0 /* DetailTalkStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailTalkStore.swift; sourceTree = ""; }; 33 | 307C76791F48091D87DA9F87 /* Pods-twitchConferences.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-twitchConferences.debug.xcconfig"; path = "Target Support Files/Pods-twitchConferences/Pods-twitchConferences.debug.xcconfig"; sourceTree = ""; }; 34 | 94EC097AA12B05BAF81B7543 /* Pods_twitchConferences.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_twitchConferences.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 35 | /* End PBXFileReference section */ 36 | 37 | /* Begin PBXFrameworksBuildPhase section */ 38 | 1BEA77B622E6046A00F1EBE0 /* Frameworks */ = { 39 | isa = PBXFrameworksBuildPhase; 40 | buildActionMask = 2147483647; 41 | files = ( 42 | 8D10FA4395855CBB0F920272 /* Pods_twitchConferences.framework in Frameworks */, 43 | ); 44 | runOnlyForDeploymentPostprocessing = 0; 45 | }; 46 | /* End PBXFrameworksBuildPhase section */ 47 | 48 | /* Begin PBXGroup section */ 49 | 1BEA77B022E6046900F1EBE0 = { 50 | isa = PBXGroup; 51 | children = ( 52 | 1BEA77BB22E6046A00F1EBE0 /* twitchConferences */, 53 | 1BEA77BA22E6046A00F1EBE0 /* Products */, 54 | DC0809C7B21F54283BA6A27E /* Pods */, 55 | 79396E38940F9FA21547C826 /* Frameworks */, 56 | ); 57 | sourceTree = ""; 58 | }; 59 | 1BEA77BA22E6046A00F1EBE0 /* Products */ = { 60 | isa = PBXGroup; 61 | children = ( 62 | 1BEA77B922E6046A00F1EBE0 /* twitchConferences.app */, 63 | ); 64 | name = Products; 65 | sourceTree = ""; 66 | }; 67 | 1BEA77BB22E6046A00F1EBE0 /* twitchConferences */ = { 68 | isa = PBXGroup; 69 | children = ( 70 | 1BEA77BC22E6046A00F1EBE0 /* AppDelegate.swift */, 71 | 1BEA77BE22E6046A00F1EBE0 /* SceneDelegate.swift */, 72 | 1BEA77DD22E60E0600F1EBE0 /* Backend */, 73 | 1BEA77D522E6067200F1EBE0 /* Views */, 74 | 1BEA77C222E6046C00F1EBE0 /* Assets.xcassets */, 75 | 1BEA77C722E6046C00F1EBE0 /* LaunchScreen.storyboard */, 76 | 1BEA77CA22E6046C00F1EBE0 /* Info.plist */, 77 | 1BEA77C422E6046C00F1EBE0 /* Preview Content */, 78 | ); 79 | path = twitchConferences; 80 | sourceTree = ""; 81 | }; 82 | 1BEA77C422E6046C00F1EBE0 /* Preview Content */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | 1BEA77C522E6046C00F1EBE0 /* Preview Assets.xcassets */, 86 | ); 87 | path = "Preview Content"; 88 | sourceTree = ""; 89 | }; 90 | 1BEA77D522E6067200F1EBE0 /* Views */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | 1BEA77D622E6067200F1EBE0 /* AddTalkView.swift */, 94 | 1BEA77C022E6046A00F1EBE0 /* ContentView.swift */, 95 | 1BEA77D722E6067200F1EBE0 /* DetailView.swift */, 96 | ); 97 | path = Views; 98 | sourceTree = ""; 99 | }; 100 | 1BEA77DD22E60E0600F1EBE0 /* Backend */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | 1BEA77DE22E60E0600F1EBE0 /* TalkStore.swift */, 104 | 1BEA77DF22E60E0600F1EBE0 /* DetailTalkStore.swift */, 105 | ); 106 | path = Backend; 107 | sourceTree = ""; 108 | }; 109 | 79396E38940F9FA21547C826 /* Frameworks */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | 94EC097AA12B05BAF81B7543 /* Pods_twitchConferences.framework */, 113 | ); 114 | name = Frameworks; 115 | sourceTree = ""; 116 | }; 117 | DC0809C7B21F54283BA6A27E /* Pods */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | 307C76791F48091D87DA9F87 /* Pods-twitchConferences.debug.xcconfig */, 121 | 176450717F6AF4D925F71864 /* Pods-twitchConferences.release.xcconfig */, 122 | ); 123 | path = Pods; 124 | sourceTree = ""; 125 | }; 126 | /* End PBXGroup section */ 127 | 128 | /* Begin PBXNativeTarget section */ 129 | 1BEA77B822E6046A00F1EBE0 /* twitchConferences */ = { 130 | isa = PBXNativeTarget; 131 | buildConfigurationList = 1BEA77CD22E6046C00F1EBE0 /* Build configuration list for PBXNativeTarget "twitchConferences" */; 132 | buildPhases = ( 133 | EEBE010F73122DEF0A897447 /* [CP] Check Pods Manifest.lock */, 134 | 1BEA77B522E6046A00F1EBE0 /* Sources */, 135 | 1BEA77B622E6046A00F1EBE0 /* Frameworks */, 136 | 1BEA77B722E6046A00F1EBE0 /* Resources */, 137 | 50B23E4E85FC7156DC42BCC9 /* [CP] Embed Pods Frameworks */, 138 | ); 139 | buildRules = ( 140 | ); 141 | dependencies = ( 142 | ); 143 | name = twitchConferences; 144 | productName = twitchConferences; 145 | productReference = 1BEA77B922E6046A00F1EBE0 /* twitchConferences.app */; 146 | productType = "com.apple.product-type.application"; 147 | }; 148 | /* End PBXNativeTarget section */ 149 | 150 | /* Begin PBXProject section */ 151 | 1BEA77B122E6046900F1EBE0 /* Project object */ = { 152 | isa = PBXProject; 153 | attributes = { 154 | LastSwiftUpdateCheck = 1100; 155 | LastUpgradeCheck = 1100; 156 | ORGANIZATIONNAME = wizage; 157 | TargetAttributes = { 158 | 1BEA77B822E6046A00F1EBE0 = { 159 | CreatedOnToolsVersion = 11.0; 160 | }; 161 | }; 162 | }; 163 | buildConfigurationList = 1BEA77B422E6046900F1EBE0 /* Build configuration list for PBXProject "twitchConferences" */; 164 | compatibilityVersion = "Xcode 9.3"; 165 | developmentRegion = en; 166 | hasScannedForEncodings = 0; 167 | knownRegions = ( 168 | en, 169 | Base, 170 | ); 171 | mainGroup = 1BEA77B022E6046900F1EBE0; 172 | productRefGroup = 1BEA77BA22E6046A00F1EBE0 /* Products */; 173 | projectDirPath = ""; 174 | projectRoot = ""; 175 | targets = ( 176 | 1BEA77B822E6046A00F1EBE0 /* twitchConferences */, 177 | ); 178 | }; 179 | /* End PBXProject section */ 180 | 181 | /* Begin PBXResourcesBuildPhase section */ 182 | 1BEA77B722E6046A00F1EBE0 /* Resources */ = { 183 | isa = PBXResourcesBuildPhase; 184 | buildActionMask = 2147483647; 185 | files = ( 186 | 1BEA77C922E6046C00F1EBE0 /* LaunchScreen.storyboard in Resources */, 187 | 1BEA77C622E6046C00F1EBE0 /* Preview Assets.xcassets in Resources */, 188 | 1BEA77C322E6046C00F1EBE0 /* Assets.xcassets in Resources */, 189 | ); 190 | runOnlyForDeploymentPostprocessing = 0; 191 | }; 192 | /* End PBXResourcesBuildPhase section */ 193 | 194 | /* Begin PBXShellScriptBuildPhase section */ 195 | 50B23E4E85FC7156DC42BCC9 /* [CP] Embed Pods Frameworks */ = { 196 | isa = PBXShellScriptBuildPhase; 197 | buildActionMask = 2147483647; 198 | files = ( 199 | ); 200 | inputFileListPaths = ( 201 | "${PODS_ROOT}/Target Support Files/Pods-twitchConferences/Pods-twitchConferences-frameworks-${CONFIGURATION}-input-files.xcfilelist", 202 | ); 203 | name = "[CP] Embed Pods Frameworks"; 204 | outputFileListPaths = ( 205 | "${PODS_ROOT}/Target Support Files/Pods-twitchConferences/Pods-twitchConferences-frameworks-${CONFIGURATION}-output-files.xcfilelist", 206 | ); 207 | runOnlyForDeploymentPostprocessing = 0; 208 | shellPath = /bin/sh; 209 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-twitchConferences/Pods-twitchConferences-frameworks.sh\"\n"; 210 | showEnvVarsInLog = 0; 211 | }; 212 | EEBE010F73122DEF0A897447 /* [CP] Check Pods Manifest.lock */ = { 213 | isa = PBXShellScriptBuildPhase; 214 | buildActionMask = 2147483647; 215 | files = ( 216 | ); 217 | inputFileListPaths = ( 218 | ); 219 | inputPaths = ( 220 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 221 | "${PODS_ROOT}/Manifest.lock", 222 | ); 223 | name = "[CP] Check Pods Manifest.lock"; 224 | outputFileListPaths = ( 225 | ); 226 | outputPaths = ( 227 | "$(DERIVED_FILE_DIR)/Pods-twitchConferences-checkManifestLockResult.txt", 228 | ); 229 | runOnlyForDeploymentPostprocessing = 0; 230 | shellPath = /bin/sh; 231 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 232 | showEnvVarsInLog = 0; 233 | }; 234 | /* End PBXShellScriptBuildPhase section */ 235 | 236 | /* Begin PBXSourcesBuildPhase section */ 237 | 1BEA77B522E6046A00F1EBE0 /* Sources */ = { 238 | isa = PBXSourcesBuildPhase; 239 | buildActionMask = 2147483647; 240 | files = ( 241 | 1BEA77BD22E6046A00F1EBE0 /* AppDelegate.swift in Sources */, 242 | 1BEA77BF22E6046A00F1EBE0 /* SceneDelegate.swift in Sources */, 243 | 1BEA77C122E6046A00F1EBE0 /* ContentView.swift in Sources */, 244 | ); 245 | runOnlyForDeploymentPostprocessing = 0; 246 | }; 247 | /* End PBXSourcesBuildPhase section */ 248 | 249 | /* Begin PBXVariantGroup section */ 250 | 1BEA77C722E6046C00F1EBE0 /* LaunchScreen.storyboard */ = { 251 | isa = PBXVariantGroup; 252 | children = ( 253 | 1BEA77C822E6046C00F1EBE0 /* Base */, 254 | ); 255 | name = LaunchScreen.storyboard; 256 | sourceTree = ""; 257 | }; 258 | /* End PBXVariantGroup section */ 259 | 260 | /* Begin XCBuildConfiguration section */ 261 | 1BEA77CB22E6046C00F1EBE0 /* Debug */ = { 262 | isa = XCBuildConfiguration; 263 | buildSettings = { 264 | ALWAYS_SEARCH_USER_PATHS = NO; 265 | CLANG_ANALYZER_NONNULL = YES; 266 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 267 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 268 | CLANG_CXX_LIBRARY = "libc++"; 269 | CLANG_ENABLE_MODULES = YES; 270 | CLANG_ENABLE_OBJC_ARC = YES; 271 | CLANG_ENABLE_OBJC_WEAK = YES; 272 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 273 | CLANG_WARN_BOOL_CONVERSION = YES; 274 | CLANG_WARN_COMMA = YES; 275 | CLANG_WARN_CONSTANT_CONVERSION = YES; 276 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 277 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 278 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 279 | CLANG_WARN_EMPTY_BODY = YES; 280 | CLANG_WARN_ENUM_CONVERSION = YES; 281 | CLANG_WARN_INFINITE_RECURSION = YES; 282 | CLANG_WARN_INT_CONVERSION = YES; 283 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 284 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 285 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 286 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 287 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 288 | CLANG_WARN_STRICT_PROTOTYPES = YES; 289 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 290 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 291 | CLANG_WARN_UNREACHABLE_CODE = YES; 292 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 293 | COPY_PHASE_STRIP = NO; 294 | DEBUG_INFORMATION_FORMAT = dwarf; 295 | ENABLE_STRICT_OBJC_MSGSEND = YES; 296 | ENABLE_TESTABILITY = YES; 297 | GCC_C_LANGUAGE_STANDARD = gnu11; 298 | GCC_DYNAMIC_NO_PIC = NO; 299 | GCC_NO_COMMON_BLOCKS = YES; 300 | GCC_OPTIMIZATION_LEVEL = 0; 301 | GCC_PREPROCESSOR_DEFINITIONS = ( 302 | "DEBUG=1", 303 | "$(inherited)", 304 | ); 305 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 306 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 307 | GCC_WARN_UNDECLARED_SELECTOR = YES; 308 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 309 | GCC_WARN_UNUSED_FUNCTION = YES; 310 | GCC_WARN_UNUSED_VARIABLE = YES; 311 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 312 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 313 | MTL_FAST_MATH = YES; 314 | ONLY_ACTIVE_ARCH = YES; 315 | SDKROOT = iphoneos; 316 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 317 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 318 | }; 319 | name = Debug; 320 | }; 321 | 1BEA77CC22E6046C00F1EBE0 /* Release */ = { 322 | isa = XCBuildConfiguration; 323 | buildSettings = { 324 | ALWAYS_SEARCH_USER_PATHS = NO; 325 | CLANG_ANALYZER_NONNULL = YES; 326 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 327 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 328 | CLANG_CXX_LIBRARY = "libc++"; 329 | CLANG_ENABLE_MODULES = YES; 330 | CLANG_ENABLE_OBJC_ARC = YES; 331 | CLANG_ENABLE_OBJC_WEAK = YES; 332 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 333 | CLANG_WARN_BOOL_CONVERSION = YES; 334 | CLANG_WARN_COMMA = YES; 335 | CLANG_WARN_CONSTANT_CONVERSION = YES; 336 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 337 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 338 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 339 | CLANG_WARN_EMPTY_BODY = YES; 340 | CLANG_WARN_ENUM_CONVERSION = YES; 341 | CLANG_WARN_INFINITE_RECURSION = YES; 342 | CLANG_WARN_INT_CONVERSION = YES; 343 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 344 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 345 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 346 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 347 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 348 | CLANG_WARN_STRICT_PROTOTYPES = YES; 349 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 350 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 351 | CLANG_WARN_UNREACHABLE_CODE = YES; 352 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 353 | COPY_PHASE_STRIP = NO; 354 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 355 | ENABLE_NS_ASSERTIONS = NO; 356 | ENABLE_STRICT_OBJC_MSGSEND = YES; 357 | GCC_C_LANGUAGE_STANDARD = gnu11; 358 | GCC_NO_COMMON_BLOCKS = YES; 359 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 360 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 361 | GCC_WARN_UNDECLARED_SELECTOR = YES; 362 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 363 | GCC_WARN_UNUSED_FUNCTION = YES; 364 | GCC_WARN_UNUSED_VARIABLE = YES; 365 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 366 | MTL_ENABLE_DEBUG_INFO = NO; 367 | MTL_FAST_MATH = YES; 368 | SDKROOT = iphoneos; 369 | SWIFT_COMPILATION_MODE = wholemodule; 370 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 371 | VALIDATE_PRODUCT = YES; 372 | }; 373 | name = Release; 374 | }; 375 | 1BEA77CE22E6046C00F1EBE0 /* Debug */ = { 376 | isa = XCBuildConfiguration; 377 | baseConfigurationReference = 307C76791F48091D87DA9F87 /* Pods-twitchConferences.debug.xcconfig */; 378 | buildSettings = { 379 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 380 | CODE_SIGN_STYLE = Automatic; 381 | DEVELOPMENT_ASSET_PATHS = "twitchConferences/Preview\\ Content"; 382 | DEVELOPMENT_TEAM = AMP8A5BDX8; 383 | ENABLE_PREVIEWS = YES; 384 | INFOPLIST_FILE = twitchConferences/Info.plist; 385 | LD_RUNPATH_SEARCH_PATHS = ( 386 | "$(inherited)", 387 | "@executable_path/Frameworks", 388 | ); 389 | PRODUCT_BUNDLE_IDENTIFIER = com.wizage.twitchConferences; 390 | PRODUCT_NAME = "$(TARGET_NAME)"; 391 | SWIFT_VERSION = 5.0; 392 | TARGETED_DEVICE_FAMILY = "1,2"; 393 | }; 394 | name = Debug; 395 | }; 396 | 1BEA77CF22E6046C00F1EBE0 /* Release */ = { 397 | isa = XCBuildConfiguration; 398 | baseConfigurationReference = 176450717F6AF4D925F71864 /* Pods-twitchConferences.release.xcconfig */; 399 | buildSettings = { 400 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 401 | CODE_SIGN_STYLE = Automatic; 402 | DEVELOPMENT_ASSET_PATHS = "twitchConferences/Preview\\ Content"; 403 | DEVELOPMENT_TEAM = AMP8A5BDX8; 404 | ENABLE_PREVIEWS = YES; 405 | INFOPLIST_FILE = twitchConferences/Info.plist; 406 | LD_RUNPATH_SEARCH_PATHS = ( 407 | "$(inherited)", 408 | "@executable_path/Frameworks", 409 | ); 410 | PRODUCT_BUNDLE_IDENTIFIER = com.wizage.twitchConferences; 411 | PRODUCT_NAME = "$(TARGET_NAME)"; 412 | SWIFT_VERSION = 5.0; 413 | TARGETED_DEVICE_FAMILY = "1,2"; 414 | }; 415 | name = Release; 416 | }; 417 | /* End XCBuildConfiguration section */ 418 | 419 | /* Begin XCConfigurationList section */ 420 | 1BEA77B422E6046900F1EBE0 /* Build configuration list for PBXProject "twitchConferences" */ = { 421 | isa = XCConfigurationList; 422 | buildConfigurations = ( 423 | 1BEA77CB22E6046C00F1EBE0 /* Debug */, 424 | 1BEA77CC22E6046C00F1EBE0 /* Release */, 425 | ); 426 | defaultConfigurationIsVisible = 0; 427 | defaultConfigurationName = Release; 428 | }; 429 | 1BEA77CD22E6046C00F1EBE0 /* Build configuration list for PBXNativeTarget "twitchConferences" */ = { 430 | isa = XCConfigurationList; 431 | buildConfigurations = ( 432 | 1BEA77CE22E6046C00F1EBE0 /* Debug */, 433 | 1BEA77CF22E6046C00F1EBE0 /* Release */, 434 | ); 435 | defaultConfigurationIsVisible = 0; 436 | defaultConfigurationName = Release; 437 | }; 438 | /* End XCConfigurationList section */ 439 | }; 440 | rootObject = 1BEA77B122E6046900F1EBE0 /* Project object */; 441 | } 442 | -------------------------------------------------------------------------------- /twitchConferences.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /twitchConferences.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /twitchConferences.xcodeproj/xcuserdata/wizage.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | twitchConferences.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 10 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /twitchConferences/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // twitchConferences 4 | // 5 | // Created by Sam Patzer on 7/22/19. 6 | // Copyright © 2019 wizage. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | // MARK: UISceneSession Lifecycle 22 | 23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 24 | // Called when a new scene session is being created. 25 | // Use this method to select a configuration to create the new scene with. 26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 27 | } 28 | 29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 30 | // Called when the user discards a scene session. 31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 33 | } 34 | 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /twitchConferences/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /twitchConferences/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /twitchConferences/Backend/DetailTalkStore.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CreateTalkStore.swift 3 | // twitchConferences 4 | // 5 | // Created by Sam Patzer on 7/22/19. 6 | // Copyright © 2019 wizage. All rights reserved. 7 | // 8 | 9 | import Combine 10 | import SwiftUI 11 | 12 | final class DetailTalkStore { 13 | 14 | init(){ 15 | 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /twitchConferences/Backend/TalkStore.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TalkStore.swift 3 | // twitchConferences 4 | // 5 | // Created by Sam Patzer on 7/22/19. 6 | // Copyright © 2019 wizage. All rights reserved. 7 | // 8 | 9 | import Combine 10 | import SwiftUI 11 | 12 | final class TalkStore { 13 | 14 | init(){ 15 | 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /twitchConferences/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /twitchConferences/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 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UILaunchStoryboardName 33 | LaunchScreen 34 | UISceneConfigurationName 35 | Default Configuration 36 | UISceneDelegateClassName 37 | $(PRODUCT_MODULE_NAME).SceneDelegate 38 | 39 | 40 | 41 | 42 | UILaunchStoryboardName 43 | LaunchScreen 44 | UIRequiredDeviceCapabilities 45 | 46 | armv7 47 | 48 | UISupportedInterfaceOrientations 49 | 50 | UIInterfaceOrientationPortrait 51 | UIInterfaceOrientationLandscapeLeft 52 | UIInterfaceOrientationLandscapeRight 53 | 54 | UISupportedInterfaceOrientations~ipad 55 | 56 | UIInterfaceOrientationPortrait 57 | UIInterfaceOrientationPortraitUpsideDown 58 | UIInterfaceOrientationLandscapeLeft 59 | UIInterfaceOrientationLandscapeRight 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /twitchConferences/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /twitchConferences/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // twitchConferences 4 | // 5 | // Created by Sam Patzer on 7/22/19. 6 | // Copyright © 2019 wizage. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftUI 11 | 12 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 18 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 19 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 20 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 21 | 22 | // Use a UIHostingController as window root view controller 23 | if let windowScene = scene as? UIWindowScene { 24 | let window = UIWindow(windowScene: windowScene) 25 | window.rootViewController = UIHostingController(rootView: ContentView()) 26 | self.window = window 27 | window.makeKeyAndVisible() 28 | } 29 | } 30 | 31 | func sceneDidDisconnect(_ scene: UIScene) { 32 | // Called as the scene is being released by the system. 33 | // This occurs shortly after the scene enters the background, or when its session is discarded. 34 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 35 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 36 | } 37 | 38 | func sceneDidBecomeActive(_ scene: UIScene) { 39 | // Called when the scene has moved from an inactive state to an active state. 40 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 41 | } 42 | 43 | func sceneWillResignActive(_ scene: UIScene) { 44 | // Called when the scene will move from an active state to an inactive state. 45 | // This may occur due to temporary interruptions (ex. an incoming phone call). 46 | } 47 | 48 | func sceneWillEnterForeground(_ scene: UIScene) { 49 | // Called as the scene transitions from the background to the foreground. 50 | // Use this method to undo the changes made on entering the background. 51 | } 52 | 53 | func sceneDidEnterBackground(_ scene: UIScene) { 54 | // Called as the scene transitions from the foreground to the background. 55 | // Use this method to save data, release shared resources, and store enough scene-specific state information 56 | // to restore the scene back to its current state. 57 | } 58 | 59 | 60 | } 61 | 62 | -------------------------------------------------------------------------------- /twitchConferences/Views/AddTalkView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AddTalkView.swift 3 | // twitchConferences 4 | // 5 | // Created by Sam Patzer on 7/22/19. 6 | // Copyright © 2019 wizage. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct AddTalkView : View { 12 | @Binding var talk : CreateTalkInput 13 | @EnvironmentObject var talkStore : TalkStore 14 | @Binding var isShowing : StateOfCreation 15 | var body: some View { 16 | Text("Hello World") 17 | } 18 | } 19 | 20 | #if DEBUG 21 | struct AddTalkView_Previews : PreviewProvider { 22 | static var previews: some View { 23 | AddTalkView() 24 | } 25 | } 26 | #endif 27 | -------------------------------------------------------------------------------- /twitchConferences/Views/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // twitchConferences 4 | // 5 | // Created by Sam Patzer on 7/22/19. 6 | // Copyright © 2019 wizage. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | enum StateOfCreation { 11 | case save 12 | case dismiss 13 | case hide 14 | case show 15 | } 16 | 17 | struct ContentView : View { 18 | @State var shouldCreate : StateOfCreation = .hide 19 | var body: some View { 20 | Text(/*@START_MENU_TOKEN@*/"Hello World!"/*@END_MENU_TOKEN@*/) 21 | } 22 | } 23 | 24 | #if DEBUG 25 | struct ContentView_Previews : PreviewProvider { 26 | static var previews: some View { 27 | ContentView() 28 | } 29 | } 30 | #endif 31 | -------------------------------------------------------------------------------- /twitchConferences/Views/DetailView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailView.swift 3 | // twitchConferences 4 | // 5 | // Created by Sam Patzer on 7/22/19. 6 | // Copyright © 2019 wizage. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct DetailView : View { 12 | var body: some View { 13 | Text(/*@START_MENU_TOKEN@*/"Hello World!"/*@END_MENU_TOKEN@*/) 14 | } 15 | } 16 | 17 | #if DEBUG 18 | struct DetailView_Previews : PreviewProvider { 19 | static var previews: some View { 20 | DetailView() 21 | } 22 | } 23 | #endif 24 | --------------------------------------------------------------------------------