{"id":18756254,"url":"https://github.com/simonberner/appetizers21","last_synced_at":"2025-11-29T21:30:13.395Z","repository":{"id":106072697,"uuid":"432103284","full_name":"simonberner/appetizers21","owner":"simonberner","description":"An iOS15 learning project 🍟 (SwiftUI, Codable, NSCache, reduce, XCTest/XCUITest)","archived":false,"fork":false,"pushed_at":"2021-12-21T08:29:14.000Z","size":846,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-12-29T01:58:51.332Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/simonberner.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-11-26T08:18:16.000Z","updated_at":"2022-01-06T19:50:40.000Z","dependencies_parsed_at":null,"dependency_job_id":"be120cf0-7706-41cd-9d7d-05a3d5fc7031","html_url":"https://github.com/simonberner/appetizers21","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simonberner%2Fappetizers21","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simonberner%2Fappetizers21/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simonberner%2Fappetizers21/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simonberner%2Fappetizers21/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/simonberner","download_url":"https://codeload.github.com/simonberner/appetizers21/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239644129,"owners_count":19673582,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-07T17:35:47.278Z","updated_at":"2025-11-29T21:30:13.340Z","avatar_url":"https://github.com/simonberner.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Appetizers21\nAppetizers21 is a take-home project to practice and learn iOS development. Learning how to fetch JSON data, caching and showing alerts,\nis the main goal of this App.\n\n## Screens\n![Home](appetizers21-home.png)\n![Home](appetizers21-account.png)\n![Home](appetizers21-order.png)\n\n## App Store\nThis App is not available on the App Store.\n\n## Used Technologies\n- Swift 5.5\n- SwiftUI\n- Codable\n- JSON\n- Combine (for getting JSON data)\n- XCTest\n### SwiftUI\n- Form, Section\n- ProgressView\n- TabView\n- Toggle\n\n## Learnings\n### General\n- We don't need [Almofire](https://github.com/Alamofire/Alamofire) to make basic network calls\n- For creating a loading view with a spinner we now can use the ProgessView() struct in SwiftUI\n- A [Gaussian Blur](https://en.wikipedia.org/wiki/Gaussian_blur) can be added to any SwiftUI View with .blur()\n- A List can be disabled with .disabled()\n- Put the @State vars as @Published into the ViewModel\n- [@ObservedObject vs @StateObject](https://medium.com/swlh/understanding-stateobject-in-swiftui-for-ios-14-98c68310154a)\n- @StateObject property wrapper persists the value during the rendering of the view\n- [Reducers](https://www.hackingwithswift.com/example-code/language/how-to-use-reduce-to-condense-an-array-into-a-single-value) let us reduce values into a single one\n- [Trainling clousure syntax can be used, when a closure is the last parameter in a function](https://www.hackingwithswift.com/example-code/language/what-is-trailing-closure-syntax)\n### SwiftUI\n- Whenever we want to update a view, we need some @State\n- [Alerts in iOS15](https://www.hackingwithswift.com/quick-start/swiftui/how-to-show-an-alert)\n- @AppStorage is SwiftUI's way of interacting with UserDefaults. It will watch an item in UserDefaults and when that changes\nit behaves like a state variable and triggers an UI update.\n- [@AppStorage has also its downsides](https://www.avanderlee.com/swift/appstorage-explained/)\n- [SwiftUI tips and tricks](https://www.hackingwithswift.com/quick-start/swiftui/swiftui-tips-and-tricks)\n### UserDefaults\n- Shall ONLY be used for storing some lightweight user preferences! \n- Gets deleted when the App is deleted! So don't save any user critical data in there!\n### NSPredicate\n- [A Predicate is a filter](https://www.hackingwithswift.com/read/38/7/examples-of-using-nspredicate-to-filter-nsfetchrequest)\n- [Core Data String Queries using NSPredicate](https://www.advancedswift.com/core-data-string-query-examples-in-swift/)\n- [PredicateKit](https://github.com/ftchirou/PredicateKit)\n### NEW async/await (introduced at WWDC21)\n- A lot of stuff gets abstracted away with async/await (not necessarly having less lines of code):\n\t- putting manually things onto the main queue/thread explicitly\n\t- completion handler handeling all the exists\n\t- doing the weak self dance\n- [Is a welcome change on the testing side of the codebase!](https://mokacoding.com/blog/how-to-test-async-await-code-in-swift/)\n### AsyncImage\n- [A simple way to download and render a remote image from a URL](https://wwdcbysundell.com/2021/using-swiftui-async-image/)\n- Pros: easy way to asynchronously load and display an Image\n- Cons: offers no cachihg of already loaded Images!\n### Property Wrappers for handling and passing data\n- @State: SwiftUI will manage the state of a struct property separately for us so that it doesn't get destroyed when a struct view gets recreated.\nWhen a @State value changes, the view invalidates is appearance and recomputes the views body. @State is used for private properties that belong\nto a specific view and never get used outside of its local scope.\n- @StateObject: used to observe an object which conforms to the ObservableObject protocol (eg. viewModel). The body of a view will be recreated\nwhen this object changes.\n- @Binding: It connects a property to a source of truth stored elsewhere and can r\u0026w its value.\n    - For prototyping, we can use a constant binding: .constant()\n- @ObservableObject: used when passing in a value (e.g. viewModel class) from outside (reference) that shall be observed. Get's destroyed when a view gets recreated!\n- @EnvironmentObject: used to observe an object which is supplied by a parent view to its ancestor views.\n- [Some differences](https://www.hackingwithswift.com/quick-start/swiftui/whats-the-difference-between-observedobject-state-and-environmentobject)\n### Optionals\n#### unwrapping\n- if let...\n- [guard let...](https://www.hackingwithswift.com/quick-start/beginners/how-to-unwrap-optionals-with-guard) is an early return to check wether something\nis valid right at the start or if we have to exit (with return) straight away.\n- [nil coalescing](https://www.hackingwithswift.com/quick-start/beginners/how-to-unwrap-optionals-with-nil-coalescing) : ```\u003coptional\u003e ?? \u003cdefaultValue\u003e```\n#### \n- [optional 'try?'](https://www.hackingwithswift.com/quick-start/beginners/how-to-handle-function-failure-with-optionals) to have a function returning\nan optional value. (The function will return nil if an error is thrown.)\n## Code comments\nFor learning purposes, I have added lots of comments alongside the code. I know that this would propably be ommitted in 'production' code ;)\n\n## Testing with XCTest/XCUITest\nAs a Tester I have the natural intrinsic  behavior of adding some Tests to my own written code. From my point of view it does not matter wether one does that\nin a TDD or 'after the code is written' fashion. As an automation engineer coming from the Selenium/Appium automation world, I am impressed how\nfast the UI Test run on a Simulator with XCTest.\nSo in this project I have for the very first time in my life ever, written some Unit- and UI-Tests with pure XCTest. Have a look and enjoy! (More to come)\n### Unit-Tests\n- [Unit tests best practices in Xcode and Swift](https://www.avanderlee.com/swift/unit-tests-best-practices/)\n- [Getting started with Unit-Testing](https://www.youtube.com/watch?v=F5aDfGNdsac)\n### UI-Tests\n- [Xcode UI Testing Cheat Sheet](https://www.hackingwithswift.com/articles/148/xcode-ui-testing-cheat-sheet)\n    - use .accessiblityIdentifier() to specify the identity of a view. It is used for testing purposes only and not for user accessibility.\n    - prefer waitForExistence(timeout:) over a regular exists check\n    - prefer using 'firstMatch' over 'element'\n\n## Credits\nThanks to Sean Allen for an amazingly well structured and tought SwiftUI Fundamentals course!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsimonberner%2Fappetizers21","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsimonberner%2Fappetizers21","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsimonberner%2Fappetizers21/lists"}