{"id":30699861,"url":"https://github.com/cewitte/flashzilla","last_synced_at":"2025-09-02T11:42:24.721Z","repository":{"id":306435481,"uuid":"1024936605","full_name":"cewitte/Flashzilla","owner":"cewitte","description":"Paul Hudson's 100 Days of SwiftUI Project 17: Flashzilla","archived":false,"fork":false,"pushed_at":"2025-08-13T16:09:44.000Z","size":5749,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-08-13T18:34:18.163Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cewitte.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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,"zenodo":null}},"created_at":"2025-07-23T13:23:02.000Z","updated_at":"2025-08-13T16:09:48.000Z","dependencies_parsed_at":"2025-07-25T19:33:18.265Z","dependency_job_id":null,"html_url":"https://github.com/cewitte/Flashzilla","commit_stats":null,"previous_names":["cewitte/flashzilla"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/cewitte/Flashzilla","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cewitte%2FFlashzilla","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cewitte%2FFlashzilla/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cewitte%2FFlashzilla/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cewitte%2FFlashzilla/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cewitte","download_url":"https://codeload.github.com/cewitte/Flashzilla/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cewitte%2FFlashzilla/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273279701,"owners_count":25077318,"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","status":"online","status_checked_at":"2025-09-02T02:00:09.530Z","response_time":77,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":"2025-09-02T11:42:22.754Z","updated_at":"2025-09-02T11:42:24.710Z","avatar_url":"https://github.com/cewitte.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Flashzilla\n\n## Paul Hudson's ([@twostraws](https://x.com/twostraws)) 100 Days of Swift UI Project 17\n\nSource URL: [link](https://www.hackingwithswift.com/books/ios-swiftui/flashzilla-introduction)\n\n\u003eIn this project we’re going to build an app that helps users learn things using flashcards – cards with one thing written on such as “to buy”, and another thing written on the other side, such as “comprar”. Of course, this is a digital app so we don’t need to worry about “the other side”, and can instead just make the detail for the flash card appear when it’s tapped.\n\n_Note: In this project, I chose to go with less branches and a shorter Read Me, focusing only on what I've found interesting or important for my own projects. One can always refer to Paul's original tutorial (always recommended) for a complete explanation._\n\n### Gestures in SwiftUI\n\nExamples of gestures _(note: it doesn't work well in the simulator)_:\n\n```swift\nimport SwiftUI\n\nstruct ContentView: View {\n    @State var tapMessage = \"Tap to start!\"\n    @State var gestureChangedMessage = \"This triggers whenever the gesture changes!\"\n    @State var scaleMessage = \"Scale gesture!\"\n    @State var currentAmount = 0.0\n    @State var finalAmount = 1.0\n    @State var rotationMessage = \"Rotation gesture!\"\n    @State var currentAngleAmount = Angle.zero\n    @State var finalAngleAmount = Angle.zero\n    \n    var body: some View {\n        Spacer()\n        \n        Text(tapMessage)\n            .onTapGesture(count: 2) {\n                tapMessage = \"You tapped 2 times\"\n            }\n            .onLongPressGesture(minimumDuration: 2) {\n                tapMessage = \"You pressed long for 2 seconds\"\n            }\n        \n        Spacer()\n        \n        Text(gestureChangedMessage)\n            .onLongPressGesture(minimumDuration: 1) {\n                gestureChangedMessage = \"Long press gesture started!\"\n            } onPressingChanged: { inProgress in\n                gestureChangedMessage = (\"In progress: \\(inProgress ? \"Long pressing\": \"Long pressing ended\")\")\n            }\n        \n        Spacer()\n        \n        Text(scaleMessage)\n            .scaleEffect(finalAmount + currentAmount)\n            .gesture(\n                MagnifyGesture()\n                    .onChanged { value in\n                        self.currentAmount = value.magnification - 1\n                    }\n                    .onEnded { value in\n                        finalAmount += currentAmount\n                        currentAmount = 0\n                    }\n            )\n        \n        Spacer()\n        \n        Text(rotationMessage)\n            .rotationEffect(currentAngleAmount + finalAngleAmount)\n            .gesture(\n                RotateGesture()\n                    .onChanged { value in\n                        self.currentAngleAmount = value.rotation\n                    }\n                    .onEnded { value in\n                        finalAngleAmount += currentAngleAmount\n                        currentAngleAmount = .zero\n                    }\n            )\n        \n        \n        Spacer()\n    }\n}\n\n#Preview {\n    ContentView()\n}\n```\n\n### Single CardView\n\nSource URL: [link](https://www.hackingwithswift.com/books/ios-swiftui/designing-a-single-card-view)\n\nResult:\n\n\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"./images/01-single-card-view.gif\" width=\"300\"/\u003e\n\u003c/div\u003e\n\n### Building a stack of cards\n\nSource URL: [link](https://www.hackingwithswift.com/books/ios-swiftui/building-a-stack-of-cards)\n\nI've found this `extension` for stacking cards really interesting:\n\n```swift\nextension View {\n    func stacked(at position: Int, in total: Int) -\u003e some View {\n        let offset = Double(total - position)\n        return self.offset(y: offset * 10)\n    }\n}\n```\n\nAnd here's the code...\n\n```swift\nZStack {\n    Image(.background)\n        .resizable()\n        .ignoresSafeArea()\n    VStack {\n        ZStack {\n            ForEach(0..\u003ccards.count, id: \\.self) { index in\n                CardView(card: cards[index])\n                    .stacked(at: index, in: cards.count)\n            }\n        }\n    }\n}\n```\n\n... that creates the end result below:\n\n\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"./images/stacked_cards.png\" width=\"300\"/\u003e\n\u003c/div\u003e\n\n### Challenge 1\n\nSource URL: [Flashzilla Wrap Up](https://www.hackingwithswift.com/books/ios-swiftui/flashzilla-wrap-up)\n\nBranch: `challenge-01`\n\n\u003eWhen adding a card, the text fields keep their current text. Fix that so that the textfields clear themselves after a card is added.\n\nThis problem was solved by simply setting the two state variables back to an empty string value after adding (and saving) the card as below:\n\n```swift\nfunc addCard() {\n    let trimmedPrompt = newPrompt.trimmingCharacters(in: .whitespaces)\n    let trimmedAnswer = newAnswer.trimmingCharacters(in: .whitespaces)\n    guard trimmedPrompt.isEmpty == false \u0026\u0026 trimmedAnswer.isEmpty == false else { return }\n\n    let card = Card(prompt: trimmedPrompt, answer: trimmedAnswer)\n    cards.insert(card, at: 0)\n    saveData()\n    \n    // Challenge 1 solution below\n    newPrompt = \"\"\n    newAnswer = \"\"\n}\n```\n\n## Acknowledgments\n\nOriginal code created by: [Paul Hudson - @twostraws](https://x.com/twostraws) (Thank you!)\n\nMade with :heart: by [@cewitte](https://x.com/cewitte)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcewitte%2Fflashzilla","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcewitte%2Fflashzilla","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcewitte%2Fflashzilla/lists"}