├── .gitignore ├── 02-getting-started └── RGBBullsEye │ ├── Design │ └── book-screenshot.png │ ├── RGBBullsEye.xcodeproj │ └── project.pbxproj │ ├── RGBBullsEye │ ├── AppDelegate.swift │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── Info.plist │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ ├── Resources │ │ └── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ │ └── Contents.json │ ├── Reusables │ │ ├── Extensions │ │ │ └── PreviewLayout+Utils.swift │ │ └── Views │ │ │ ├── ColorSlider.swift │ │ │ └── ColorSliders.swift │ ├── SceneDelegate.swift │ └── Scenes │ │ └── MainView.swift │ └── Screenshots │ └── recording-1.gif ├── 03-understanding-swiftui └── RGBBullsEye │ ├── Design │ └── book-screenshot.png │ ├── RGBBullsEye.xcodeproj │ └── project.pbxproj │ ├── RGBBullsEye │ ├── AppDelegate.swift │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── Info.plist │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ ├── Resources │ │ └── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ │ └── Contents.json │ ├── Reusables │ │ ├── Extensions │ │ │ └── PreviewLayout+Utils.swift │ │ ├── TimeCounter.swift │ │ └── Views │ │ │ ├── ColorSlider.swift │ │ │ └── ColorSliders.swift │ ├── SceneDelegate.swift │ └── Scenes │ │ └── MainView.swift │ └── Screenshots │ └── recording-1.gif ├── 04-integrating-swiftui └── RGBBullsEye │ ├── Design │ └── book-screenshot.png │ ├── RGBBullsEye.xcodeproj │ └── project.pbxproj │ ├── RGBBullsEye │ ├── AppDelegate.swift │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── Info.plist │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ ├── Resources │ │ └── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ │ └── Contents.json │ ├── Reusables │ │ ├── Extensions │ │ │ └── PreviewLayout+Utils.swift │ │ ├── TimeCounter.swift │ │ └── Views │ │ │ ├── ColorSlider.swift │ │ │ ├── ColorSliders.swift │ │ │ └── ColorUISlider.swift │ ├── SceneDelegate.swift │ └── Scenes │ │ └── MainView.swift │ └── Screenshots │ └── recording-1.gif ├── 05-the-apple-ecosystem ├── BullsEyePlus │ ├── BullsEyePlus WatchKit App │ │ ├── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── Base.lproj │ │ │ └── Interface.storyboard │ │ └── Info.plist │ ├── BullsEyePlus WatchKit Extension │ │ ├── Assets.xcassets │ │ │ ├── Complication.complicationset │ │ │ │ ├── Circular.imageset │ │ │ │ │ └── Contents.json │ │ │ │ ├── Contents.json │ │ │ │ ├── Extra Large.imageset │ │ │ │ │ └── Contents.json │ │ │ │ ├── Graphic Bezel.imageset │ │ │ │ │ └── Contents.json │ │ │ │ ├── Graphic Circular.imageset │ │ │ │ │ └── Contents.json │ │ │ │ ├── Graphic Corner.imageset │ │ │ │ │ └── Contents.json │ │ │ │ ├── Graphic Large Rectangular.imageset │ │ │ │ │ └── Contents.json │ │ │ │ ├── Modular.imageset │ │ │ │ │ └── Contents.json │ │ │ │ └── Utilitarian.imageset │ │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── ContentView.swift │ │ ├── ExtensionDelegate.swift │ │ ├── HostingController.swift │ │ ├── Info.plist │ │ └── Preview Content │ │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ ├── BullsEyePlus.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ ├── BullsEyePlus WatchKit App.xcscheme │ │ │ └── BullsEyePlus.xcscheme │ └── BullsEyePlus │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon1024.png │ │ │ ├── Icon20.png │ │ │ ├── Icon20@2x.png │ │ │ ├── Icon29.png │ │ │ ├── Icon29@2x.png │ │ │ ├── Icon40.png │ │ │ ├── Icon40@2x.png │ │ │ ├── Icon76.png │ │ │ ├── Icon76@2x.png │ │ │ ├── Icon83.5@2x.png │ │ │ ├── icon-app-20@2x.png │ │ │ ├── icon-app-20@3x.png │ │ │ ├── icon-app-29@2x.png │ │ │ ├── icon-app-29@3x copy.png │ │ │ ├── icon-app-40@2x.png │ │ │ ├── icon-app-40@3x.png │ │ │ ├── icon-app-60@2x.png │ │ │ └── icon-app-60@3x.png │ │ ├── Contents.json │ │ ├── colors │ │ │ ├── Contents.json │ │ │ ├── rw-dark.colorset │ │ │ │ └── Contents.json │ │ │ ├── rw-green.colorset │ │ │ │ └── Contents.json │ │ │ └── rw-light.colorset │ │ │ │ └── Contents.json │ │ └── launch-assets │ │ │ ├── Contents.json │ │ │ ├── rw-logo.imageset │ │ │ ├── Contents.json │ │ │ └── Razewarelogo_1024.png │ │ │ └── rwdevcon-bg.imageset │ │ │ ├── Contents.json │ │ │ └── rwdevcon-bg.png │ │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ │ ├── BullsEyePlus.entitlements │ │ ├── ContentView.swift │ │ ├── Info.plist │ │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ │ ├── Reusables │ │ └── Extensions │ │ │ └── UserDefaults+ObservableObject.swift │ │ ├── SceneDelegate.swift │ │ └── Settings.bundle │ │ ├── Root.plist │ │ └── en.lproj │ │ └── Root.strings ├── MacBullseye │ └── MacBullseye │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ │ ├── Base.lproj │ │ └── Main.storyboard │ │ ├── Info.plist │ │ ├── MacBullseye.entitlements │ │ └── Preview Content │ │ └── Preview Assets.xcassets │ │ └── Contents.json ├── Packages │ ├── Game │ │ ├── .gitignore │ │ ├── .swiftpm │ │ │ └── xcode │ │ │ │ └── xcshareddata │ │ │ │ └── xcschemes │ │ │ │ └── Game.xcscheme │ │ ├── Package.swift │ │ ├── README.md │ │ ├── Sources │ │ │ └── Game │ │ │ │ └── BullsEyeGame.swift │ │ └── Tests │ │ │ ├── GameTests │ │ │ ├── GameTests.swift │ │ │ └── XCTestManifests.swift │ │ │ └── LinuxMain.swift │ └── GameViewKit │ │ ├── .gitignore │ │ ├── Package.swift │ │ ├── README.md │ │ ├── Sources │ │ └── GameViewKit │ │ │ └── GameView.swift │ │ └── Tests │ │ ├── GameViewKitTests │ │ ├── GameViewKitTests.swift │ │ └── XCTestManifests.swift │ │ └── LinuxMain.swift ├── RGBBullsEye │ ├── Design │ │ └── book-screenshot.png │ ├── RGBBullsEye.xcodeproj │ │ ├── project.pbxproj │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── RGBBullsEye (Chapter 5).xcscheme │ ├── RGBBullsEye │ │ ├── AppDelegate.swift │ │ ├── Base.lproj │ │ │ └── LaunchScreen.storyboard │ │ ├── Info.plist │ │ ├── Preview Content │ │ │ └── Preview Assets.xcassets │ │ │ │ └── Contents.json │ │ ├── Resources │ │ │ └── Assets.xcassets │ │ │ │ ├── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ ├── Reusables │ │ │ ├── Extensions │ │ │ │ └── PreviewLayout+Utils.swift │ │ │ ├── Models │ │ │ │ └── Game.swift │ │ │ ├── TimeCounter.swift │ │ │ └── Views │ │ │ │ ├── ColorSlider.swift │ │ │ │ ├── ColorSliders.swift │ │ │ │ └── ColorUISlider.swift │ │ ├── SceneDelegate.swift │ │ └── Scenes │ │ │ └── MainView.swift │ └── Screenshots │ │ └── recording-1.gif └── TVBullseye │ ├── TVBullseye.xcodeproj │ ├── project.pbxproj │ └── xcshareddata │ │ └── xcschemes │ │ └── TVBullseye.xcscheme │ └── TVBullseye │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── App Icon & Top Shelf Image.brandassets │ │ ├── App Icon - App Store.imagestack │ │ │ ├── Back.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── Front.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ └── Middle.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ ├── App Icon.imagestack │ │ │ ├── Back.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── Front.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ └── Middle.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── Top Shelf Image Wide.imageset │ │ │ └── Contents.json │ │ └── Top Shelf Image.imageset │ │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── ContentView.swift │ ├── Info.plist │ └── Preview Content │ └── Preview Assets.xcassets │ └── Contents.json ├── 06-intro-to-controls-text-and-image └── Kuchi │ ├── Kuchi.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ ├── Kuchi │ ├── AppDelegate.swift │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── Info.plist │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ ├── Resources │ │ └── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ └── Images │ │ │ ├── Contents.json │ │ │ └── swift_world.imageset │ │ │ ├── Contents.json │ │ │ └── swift_world.png │ ├── SceneDelegate.swift │ └── Scenes │ │ ├── EntryView.swift │ │ ├── Home │ │ └── HomeView.swift │ │ ├── Registration │ │ └── RegistrationView.swift │ │ └── Welcome │ │ └── WelcomeView.swift │ └── Resources │ └── Images │ └── swift_world.png ├── 07-state-and-data-flow └── Kuchi │ ├── Kuchi.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved │ ├── Kuchi │ ├── AppDelegate.swift │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── Data │ │ └── Models │ │ │ ├── User+Profile.swift │ │ │ └── User.swift │ ├── Info.plist │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ ├── Resources │ │ └── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ └── Images │ │ │ ├── Contents.json │ │ │ └── swift_world.imageset │ │ │ ├── Contents.json │ │ │ └── swift_world.png │ ├── SceneDelegate.swift │ └── Scenes │ │ ├── EntryView.swift │ │ ├── Home │ │ ├── CongratulationsView.swift │ │ ├── HomeView.swift │ │ └── ProgressView.swift │ │ ├── Practice │ │ ├── ChallengeView.swift │ │ ├── ChallengeViewModel.swift │ │ ├── ChoicesView.swift │ │ ├── DeckBuilder.swift │ │ ├── PracticeContainerView.swift │ │ ├── QuestionView.swift │ │ └── ScoreView.swift │ │ ├── Profile │ │ └── ProfileView.swift │ │ ├── Registration │ │ └── RegistrationView.swift │ │ └── Welcome │ │ └── WelcomeView.swift │ ├── Packages │ ├── LanguageLearning │ │ ├── .gitignore │ │ ├── .swiftpm │ │ │ └── xcode │ │ │ │ └── package.xcworkspace │ │ │ │ └── contents.xcworkspacedata │ │ ├── Package.swift │ │ ├── README.md │ │ ├── Sources │ │ │ ├── Assessing │ │ │ │ └── WordAssessment.swift │ │ │ └── Learning │ │ │ │ └── WordCard.swift │ │ └── Tests │ │ │ ├── LearningTests │ │ │ └── LearningTests.swift │ │ │ └── LinuxMain.swift │ └── Languages │ │ ├── .gitignore │ │ ├── Package.swift │ │ ├── README.md │ │ ├── Sources │ │ └── Languages │ │ │ └── Languages.swift │ │ └── Tests │ │ ├── LanguagesTests │ │ ├── LanguagesTests.swift │ │ └── XCTestManifests.swift │ │ └── LinuxMain.swift │ └── Resources │ ├── Images │ └── swift_world.png │ └── jp.json ├── 08-controls-and-user-input └── Kuchi │ ├── Kuchi.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved │ ├── Kuchi │ ├── AppDelegate.swift │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── Data │ │ └── Models │ │ │ ├── UserProfile.swift │ │ │ ├── UserSettings.swift │ │ │ └── UserStore.swift │ ├── Info.plist │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ ├── Resources │ │ └── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ └── Images │ │ │ ├── Contents.json │ │ │ └── swift_world.imageset │ │ │ ├── Contents.json │ │ │ └── swift_world.png │ ├── Reusables │ │ ├── Extensions │ │ │ ├── Text+TextStyleWithViewModifier.swift │ │ │ └── TextField+TextFieldStyleWithViewModifier.swift │ │ ├── Styles │ │ │ └── Buttons │ │ │ │ ├── CustomFilledButtonStyle.swift │ │ │ │ └── CustomOutlinedButtonStyle.swift │ │ ├── View Components │ │ │ └── WelcomeMessageView.swift │ │ ├── View Modifiers │ │ │ └── Text Field Styles │ │ │ │ └── CustomRoundedTextFieldStyle.swift │ │ └── Views │ │ │ ├── LogoImage.swift │ │ │ └── WelcomeBackgroundImage.swift │ ├── SceneDelegate.swift │ └── Scenes │ │ ├── EntryView.swift │ │ ├── Home │ │ ├── CongratulationsView.swift │ │ ├── HomeView.swift │ │ └── ProgressView.swift │ │ ├── Practice │ │ ├── ChallengeView.swift │ │ ├── ChallengeViewModel.swift │ │ ├── ChoicesView.swift │ │ ├── DeckBuilder.swift │ │ ├── PracticeContainerView.swift │ │ ├── QuestionView.swift │ │ └── ScoreView.swift │ │ ├── Profile │ │ └── ProfileView.swift │ │ ├── Registration │ │ ├── RegistrationView.swift │ │ └── RegistrationViewModel.swift │ │ └── Welcome │ │ └── WelcomeView.swift │ ├── Packages │ ├── LanguageLearning │ │ ├── .gitignore │ │ ├── .swiftpm │ │ │ └── xcode │ │ │ │ └── package.xcworkspace │ │ │ │ └── contents.xcworkspacedata │ │ ├── Package.swift │ │ ├── README.md │ │ ├── Sources │ │ │ ├── Assessing │ │ │ │ └── WordAssessment.swift │ │ │ └── Learning │ │ │ │ └── WordCard.swift │ │ └── Tests │ │ │ ├── LearningTests │ │ │ └── LearningTests.swift │ │ │ └── LinuxMain.swift │ └── Languages │ │ ├── .gitignore │ │ ├── Package.swift │ │ ├── README.md │ │ ├── Sources │ │ └── Languages │ │ │ └── Languages.swift │ │ └── Tests │ │ ├── LanguagesTests │ │ ├── LanguagesTests.swift │ │ └── XCTestManifests.swift │ │ └── LinuxMain.swift │ └── Resources │ ├── Images │ └── swift_world.png │ └── jp.json ├── 09-stacks-and-containers └── Kuchi │ ├── Kuchi.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved │ ├── Kuchi │ ├── AppDelegate.swift │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── Data │ │ ├── Models │ │ │ ├── User.swift │ │ │ └── UserProfile.swift │ │ └── State │ │ │ ├── AppState.swift │ │ │ ├── SettingsState.swift │ │ │ ├── UserProfileState.swift │ │ │ └── UserState.swift │ ├── Info.plist │ ├── Preview Content │ │ ├── Preview Assets.xcassets │ │ │ └── Contents.json │ │ └── Sample Data │ │ │ ├── ChallengeWords.swift │ │ │ └── Samples.swift │ ├── Resources │ │ └── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ └── Images │ │ │ ├── Contents.json │ │ │ └── swift_world.imageset │ │ │ ├── Contents.json │ │ │ └── swift_world.png │ ├── Reusables │ │ ├── Constants │ │ │ └── UserDefaultsKey.swift │ │ ├── Extensions │ │ │ ├── Text+TextStyleWithViewModifier.swift │ │ │ └── TextField+TextFieldStyleWithViewModifier.swift │ │ ├── Styles │ │ │ └── Buttons │ │ │ │ ├── CustomFilledButtonStyle.swift │ │ │ │ └── CustomOutlinedButtonStyle.swift │ │ ├── View Components │ │ │ └── WelcomeMessageView.swift │ │ ├── View Modifiers │ │ │ └── Text Field Styles │ │ │ │ └── CustomRoundedTextFieldStyle.swift │ │ └── Views │ │ │ ├── LogoImage.swift │ │ │ ├── UserBanner.swift │ │ │ └── WelcomeBackgroundImage.swift │ ├── SceneDelegate.swift │ └── Scenes │ │ ├── EntryView.swift │ │ ├── Home │ │ ├── CongratulationsView.swift │ │ ├── HomeView.swift │ │ └── ProgressView.swift │ │ ├── Practice │ │ ├── ChallengeView.swift │ │ ├── ChallengeViewModel.swift │ │ ├── ChoicesView.swift │ │ ├── DeckBuilder.swift │ │ ├── PracticeContainerView.swift │ │ ├── QuestionView.swift │ │ └── ScoreView.swift │ │ ├── Profile │ │ └── ProfileView.swift │ │ ├── Registration │ │ ├── RegistrationView.swift │ │ └── RegistrationViewModel.swift │ │ └── Welcome │ │ └── WelcomeView.swift │ ├── Packages │ ├── LanguageLearning │ │ ├── .gitignore │ │ ├── .swiftpm │ │ │ └── xcode │ │ │ │ └── package.xcworkspace │ │ │ │ └── contents.xcworkspacedata │ │ ├── Package.swift │ │ ├── README.md │ │ ├── Sources │ │ │ ├── Assessing │ │ │ │ └── WordAssessment.swift │ │ │ └── Learning │ │ │ │ └── WordCard.swift │ │ └── Tests │ │ │ ├── LearningTests │ │ │ └── LearningTests.swift │ │ │ └── LinuxMain.swift │ └── Languages │ │ ├── .gitignore │ │ ├── Package.swift │ │ ├── README.md │ │ ├── Sources │ │ └── Languages │ │ │ └── Languages.swift │ │ └── Tests │ │ ├── LanguagesTests │ │ ├── LanguagesTests.swift │ │ └── XCTestManifests.swift │ │ └── LinuxMain.swift │ └── Resources │ ├── Images │ └── swift_world.png │ └── jp.json ├── 10-lists-and-navigation └── MountainAirport │ ├── MountainAirport.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved │ └── MountainAirport │ ├── AppDelegate.swift │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── Data │ ├── Constants │ │ ├── FlightDirection.swift │ │ └── FlightStatus.swift │ ├── Models │ │ ├── FlightHistory.swift │ │ └── FlightInformation.swift │ └── State │ │ ├── AppState.swift │ │ └── FlightInformationState.swift │ ├── Info.plist │ ├── Preview Content │ ├── Preview Assets.xcassets │ │ └── Contents.json │ └── Sample Data │ │ └── SampleData.swift │ ├── Resources │ └── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ └── Contents.json │ │ └── Contents.json │ ├── SceneDelegate.swift │ └── Scenes │ ├── EntryView.swift │ ├── Flight Board │ ├── FlightBoard.swift │ ├── FlightBoardContainerView.swift │ ├── FlightBoardListItem.swift │ ├── FlightBoardViewModel.swift │ └── Item Details │ │ └── FlightBoardItemDetails.swift │ └── StackEntryView.swift ├── 11-building-for-testability └── BookProjects │ └── SwiftCalc │ ├── SwiftCalc.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ ├── IDETemplateMacros.plist │ │ └── xcschemes │ │ └── MyRWTutorial.xcscheme │ ├── SwiftCalc │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon1024.png │ │ │ ├── Icon20.png │ │ │ ├── Icon20@2x.png │ │ │ ├── Icon29.png │ │ │ ├── Icon29@2x.png │ │ │ ├── Icon40.png │ │ │ ├── Icon40@2x.png │ │ │ ├── Icon76.png │ │ │ ├── Icon76@2x.png │ │ │ ├── Icon83.5@2x.png │ │ │ ├── icon-app-20@2x.png │ │ │ ├── icon-app-20@3x.png │ │ │ ├── icon-app-29@2x.png │ │ │ ├── icon-app-29@3x copy.png │ │ │ ├── icon-app-40@2x.png │ │ │ ├── icon-app-40@3x.png │ │ │ ├── icon-app-60@2x.png │ │ │ └── icon-app-60@3x.png │ │ ├── Contents.json │ │ ├── colors │ │ │ ├── Contents.json │ │ │ ├── rw-dark.colorset │ │ │ │ └── Contents.json │ │ │ ├── rw-green.colorset │ │ │ │ └── Contents.json │ │ │ └── rw-light.colorset │ │ │ │ └── Contents.json │ │ └── launch-assets │ │ │ ├── Contents.json │ │ │ ├── rw-logo.imageset │ │ │ ├── Contents.json │ │ │ └── Razewarelogo_1024.png │ │ │ └── rwdevcon-bg.imageset │ │ │ ├── Contents.json │ │ │ └── rwdevcon-bg.png │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── ContentView.swift │ ├── Info.plist │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ ├── SceneDelegate.swift │ └── SwiftCalc.entitlements │ └── SwiftCalcUITests │ ├── Info.plist │ └── SwiftCalcUITests.swift ├── 12-handling-user-input └── Kuchi │ ├── Kuchi.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved │ ├── Kuchi │ ├── AppDelegate.swift │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── Data │ │ ├── Models │ │ │ ├── FlashCard │ │ │ │ ├── FlashCard+Equatable.swift │ │ │ │ ├── FlashCard.swift │ │ │ │ └── FlashCardDeck.swift │ │ │ ├── User.swift │ │ │ └── UserProfile.swift │ │ └── State │ │ │ ├── AppState.swift │ │ │ ├── LearningState.swift │ │ │ ├── SettingsState.swift │ │ │ ├── UserProfileState.swift │ │ │ └── UserState.swift │ ├── Info.plist │ ├── Preview Content │ │ ├── Preview Assets.xcassets │ │ │ └── Contents.json │ │ └── Sample Data │ │ │ ├── SampleFlashCardDeck.swift │ │ │ └── Samples.swift │ ├── Resources │ │ └── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ └── Images │ │ │ ├── Contents.json │ │ │ └── swift_world.imageset │ │ │ ├── Contents.json │ │ │ └── swift_world.png │ ├── Reusables │ │ ├── Constants │ │ │ └── UserDefaultsKey.swift │ │ ├── Extensions │ │ │ ├── Text+TextStyleWithViewModifier.swift │ │ │ └── TextField+TextFieldStyleWithViewModifier.swift │ │ ├── Gestures │ │ │ └── CardDragDirection.swift │ │ ├── Styles │ │ │ └── Buttons │ │ │ │ ├── CustomFilledButtonStyle.swift │ │ │ │ └── CustomOutlinedButtonStyle.swift │ │ ├── View Components │ │ │ └── WelcomeMessageView.swift │ │ ├── View Modifiers │ │ │ └── Text Field Styles │ │ │ │ └── CustomRoundedTextFieldStyle.swift │ │ └── Views │ │ │ ├── Flash Card │ │ │ ├── FlashCardView+ViewModel.swift │ │ │ └── FlashCardView.swift │ │ │ ├── LogoImage.swift │ │ │ ├── UserBanner.swift │ │ │ └── WelcomeBackgroundImage.swift │ ├── SceneDelegate.swift │ └── Scenes │ │ ├── EntryView.swift │ │ ├── Home │ │ ├── CongratulationsView.swift │ │ ├── HomeView.swift │ │ └── ProgressView.swift │ │ ├── Learn │ │ ├── FlashCardDeckView.swift │ │ ├── LearningContainerView.swift │ │ ├── LearningView.swift │ │ └── LearningViewModel.swift │ │ ├── Practice │ │ ├── ChallengeView.swift │ │ ├── ChallengeViewModel.swift │ │ ├── ChoicesView.swift │ │ ├── DeckBuilder.swift │ │ ├── PracticeContainerView.swift │ │ ├── QuestionView.swift │ │ └── ScoreView.swift │ │ ├── Profile │ │ └── ProfileView.swift │ │ ├── Registration │ │ ├── RegistrationView.swift │ │ └── RegistrationViewModel.swift │ │ └── Welcome │ │ └── WelcomeView.swift │ ├── Packages │ ├── LanguageLearning │ │ ├── .gitignore │ │ ├── .swiftpm │ │ │ └── xcode │ │ │ │ └── package.xcworkspace │ │ │ │ └── contents.xcworkspacedata │ │ ├── Package.swift │ │ ├── README.md │ │ ├── Sources │ │ │ ├── Assessing │ │ │ │ └── WordAssessment.swift │ │ │ └── Learning │ │ │ │ └── WordCard.swift │ │ └── Tests │ │ │ ├── LearningTests │ │ │ └── LearningTests.swift │ │ │ └── LinuxMain.swift │ └── Languages │ │ ├── .gitignore │ │ ├── Package.swift │ │ ├── README.md │ │ ├── Sources │ │ └── Languages │ │ │ └── Languages.swift │ │ └── Tests │ │ ├── LanguagesTests │ │ ├── LanguagesTests.swift │ │ └── XCTestManifests.swift │ │ └── LinuxMain.swift │ └── Resources │ ├── Images │ └── swift_world.png │ └── jp.json ├── 13-drawing-and-custom-graphics └── MountainAirport │ ├── MountainAirport.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved │ └── MountainAirport │ ├── AppDelegate.swift │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── Data │ ├── Constants │ │ ├── FlightDirection.swift │ │ └── FlightStatus.swift │ ├── Models │ │ ├── Awards │ │ │ ├── Award+BadgeView.swift │ │ │ ├── Award+Computeds.swift │ │ │ └── Award.swift │ │ ├── FlightHistory.swift │ │ └── FlightInformation.swift │ └── State │ │ ├── AppState.swift │ │ ├── AwardsState.swift │ │ └── FlightInformationState.swift │ ├── Info.plist │ ├── Preview Content │ ├── Preview Assets.xcassets │ │ └── Contents.json │ └── Sample Data │ │ └── SampleData.swift │ ├── Resources │ └── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── terminal-a-map.imageset │ │ ├── Contents.json │ │ └── terminal-a-map.png │ │ └── terminal-b-map.imageset │ │ ├── Contents.json │ │ └── terminal-b-map.png │ ├── Reusables │ └── Views │ │ └── Award Badges │ │ ├── AirportLoungeAwardView.swift │ │ ├── FirstVisitAwardView.swift │ │ └── RideShareToAirportAwardView.swift │ ├── SceneDelegate.swift │ └── Scenes │ ├── Awards │ ├── AwardsContainerView.swift │ ├── AwardsListView+ViewModel.swift │ └── AwardsListView.swift │ ├── EntryView.swift │ ├── Flight Board │ ├── FlightBoard.swift │ ├── FlightBoardContainerView.swift │ ├── FlightBoardListItem.swift │ ├── FlightBoardViewModel.swift │ └── Item Details │ │ ├── FlightBoardItemDetails+ViewModel.swift │ │ ├── FlightBoardItemDetails.swift │ │ └── Gate Details │ │ ├── FlightGateDetailsView+ViewModel.swift │ │ └── FlightGateDetailsView.swift │ └── StackEntryView.swift ├── 14-animations └── MountainAirport │ ├── MountainAirport.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved │ └── MountainAirport │ ├── AppDelegate.swift │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── Data │ ├── Constants │ │ ├── FlightDirection.swift │ │ └── FlightStatus.swift │ ├── Models │ │ ├── Awards │ │ │ ├── Award+BadgeView.swift │ │ │ ├── Award+Computeds.swift │ │ │ └── Award.swift │ │ ├── FlightHistory.swift │ │ └── FlightInformation.swift │ └── State │ │ ├── AppState.swift │ │ ├── AwardsState.swift │ │ └── FlightInformationState.swift │ ├── Info.plist │ ├── Preview Content │ ├── Preview Assets.xcassets │ │ └── Contents.json │ └── Sample Data │ │ └── SampleData.swift │ ├── Resources │ └── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── terminal-a-map.imageset │ │ ├── Contents.json │ │ └── terminal-a-map.png │ │ └── terminal-b-map.imageset │ │ ├── Contents.json │ │ └── terminal-b-map.png │ ├── Reusables │ └── Views │ │ └── Award Badges │ │ ├── AirportLoungeAwardView.swift │ │ ├── FirstVisitAwardView.swift │ │ └── RideShareToAirportAwardView.swift │ ├── SceneDelegate.swift │ └── Scenes │ ├── Awards │ ├── AwardsContainerView.swift │ ├── AwardsListView+ViewModel.swift │ └── AwardsListView.swift │ ├── EntryView.swift │ ├── Flight Board │ ├── FlightBoard.swift │ ├── FlightBoardContainerView.swift │ ├── FlightBoardListItem.swift │ ├── FlightBoardViewModel.swift │ └── Item Details │ │ ├── FlightBoardItemDetails+ViewModel.swift │ │ ├── FlightBoardItemDetails.swift │ │ └── Gate Details │ │ ├── FlightGateDetailsView+ViewModel.swift │ │ └── FlightGateDetailsView.swift │ └── StackEntryView.swift ├── 15-complex-interfaces └── MountainAirport │ ├── MountainAirport.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved │ ├── MountainAirport │ ├── AppDelegate.swift │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── Data │ │ ├── Constants │ │ │ ├── FlightDirection.swift │ │ │ └── FlightStatus.swift │ │ ├── Models │ │ │ ├── Awards │ │ │ │ ├── Award+BadgeView.swift │ │ │ │ ├── Award+Computeds.swift │ │ │ │ └── Award.swift │ │ │ ├── FlightHistory.swift │ │ │ └── FlightInformation.swift │ │ └── State │ │ │ ├── AppState.swift │ │ │ ├── AwardsState.swift │ │ │ └── FlightInformationState.swift │ ├── Info.plist │ ├── Preview Content │ │ ├── Preview Assets.xcassets │ │ │ └── Contents.json │ │ └── Sample Data │ │ │ └── SampleData.swift │ ├── Resources │ │ └── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── terminal-a-map.imageset │ │ │ ├── Contents.json │ │ │ └── terminal-a-map.png │ │ │ └── terminal-b-map.imageset │ │ │ ├── Contents.json │ │ │ └── terminal-b-map.png │ ├── Reusables │ │ └── Views │ │ │ ├── Award Badges │ │ │ ├── AirportLoungeAwardView.swift │ │ │ ├── FirstVisitAwardView.swift │ │ │ └── RideShareToAirportAwardView.swift │ │ │ └── Grid View │ │ │ └── GridView.swift │ ├── SceneDelegate.swift │ └── Scenes │ │ ├── Awards │ │ ├── Awards Grid │ │ │ ├── AwardsGridView+ViewModel.swift │ │ │ └── AwardsGridView.swift │ │ ├── Awards List │ │ │ ├── AwardsListView+ViewModel.swift │ │ │ └── AwardsListView.swift │ │ └── AwardsContainerView.swift │ │ ├── DemoIntroView.swift │ │ ├── EntryView.swift │ │ ├── Flight Board │ │ ├── FlightBoard.swift │ │ ├── FlightBoardContainerView.swift │ │ ├── FlightBoardViewModel.swift │ │ ├── Item Details │ │ │ ├── FlightBoardItemDetails+ViewModel.swift │ │ │ ├── FlightBoardItemDetails.swift │ │ │ └── Gate Details │ │ │ │ ├── FlightGateDetailsView+ViewModel.swift │ │ │ │ └── FlightGateDetailsView.swift │ │ └── List Item │ │ │ ├── FlightBoardListItem.swift │ │ │ ├── FlightBoardTimelineListItemView+ViewModel.swift │ │ │ └── FlightBoardTimelineListItemView.swift │ │ ├── GridViewDemo.swift │ │ └── StackEntryView.swift │ └── Screenshots │ └── swiftui-grid-view.mp4 ├── README.md └── SwiftUIByTutorials.xcworkspace ├── contents.xcworkspacedata └── xcshareddata └── IDEWorkspaceChecks.plist /02-getting-started/RGBBullsEye/Design/book-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/02-getting-started/RGBBullsEye/Design/book-screenshot.png -------------------------------------------------------------------------------- /02-getting-started/RGBBullsEye/RGBBullsEye/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /02-getting-started/RGBBullsEye/RGBBullsEye/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /02-getting-started/RGBBullsEye/RGBBullsEye/Reusables/Extensions/PreviewLayout+Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreviewProvider+Utils.swift 3 | // RGBBullsEye 4 | // 5 | // Created by Brian Sipple on 10/7/19. 6 | // Copyright © 2019 CypherPoet. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | 13 | extension PreviewLayout { 14 | static var iPhoneSELandscape: PreviewLayout = .fixed(width: 568, height: 320) 15 | static var iPhone11Landscape: PreviewLayout = .fixed(width: 896, height: 414) 16 | } 17 | -------------------------------------------------------------------------------- /02-getting-started/RGBBullsEye/Screenshots/recording-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/02-getting-started/RGBBullsEye/Screenshots/recording-1.gif -------------------------------------------------------------------------------- /03-understanding-swiftui/RGBBullsEye/Design/book-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/03-understanding-swiftui/RGBBullsEye/Design/book-screenshot.png -------------------------------------------------------------------------------- /03-understanding-swiftui/RGBBullsEye/RGBBullsEye/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /03-understanding-swiftui/RGBBullsEye/RGBBullsEye/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /03-understanding-swiftui/RGBBullsEye/RGBBullsEye/Reusables/Extensions/PreviewLayout+Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreviewProvider+Utils.swift 3 | // RGBBullsEye 4 | // 5 | // Created by Brian Sipple on 10/7/19. 6 | // Copyright © 2019 CypherPoet. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | 13 | extension PreviewLayout { 14 | static var iPhoneSELandscape: PreviewLayout = .fixed(width: 568, height: 320) 15 | static var iPhone11Landscape: PreviewLayout = .fixed(width: 896, height: 414) 16 | } 17 | -------------------------------------------------------------------------------- /03-understanding-swiftui/RGBBullsEye/RGBBullsEye/Reusables/TimeCounter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TimeCounter.swift 3 | // RGBBullsEye 4 | // 5 | // Created by Brian Sipple on 10/9/19. 6 | // Copyright © 2019 CypherPoet. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Combine 11 | 12 | 13 | final class TimeCounter: ObservableObject { 14 | let objectWillChange = PassthroughSubject() 15 | 16 | var timer: Timer? 17 | var counter = 0 18 | 19 | 20 | @objc func updateCounter() { 21 | counter += 1 22 | objectWillChange.send(self) 23 | } 24 | 25 | 26 | init() { 27 | timer = Timer.scheduledTimer( 28 | timeInterval: 1, 29 | target: self, 30 | selector: #selector(TimeCounter.updateCounter), 31 | userInfo: nil, 32 | repeats: true 33 | ) 34 | } 35 | 36 | 37 | func killTimer() { 38 | timer?.invalidate() 39 | timer = nil 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /03-understanding-swiftui/RGBBullsEye/Screenshots/recording-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/03-understanding-swiftui/RGBBullsEye/Screenshots/recording-1.gif -------------------------------------------------------------------------------- /04-integrating-swiftui/RGBBullsEye/Design/book-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/04-integrating-swiftui/RGBBullsEye/Design/book-screenshot.png -------------------------------------------------------------------------------- /04-integrating-swiftui/RGBBullsEye/RGBBullsEye/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /04-integrating-swiftui/RGBBullsEye/RGBBullsEye/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /04-integrating-swiftui/RGBBullsEye/RGBBullsEye/Reusables/Extensions/PreviewLayout+Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreviewProvider+Utils.swift 3 | // RGBBullsEye 4 | // 5 | // Created by Brian Sipple on 10/7/19. 6 | // Copyright © 2019 CypherPoet. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | 13 | extension PreviewLayout { 14 | static var iPhoneSELandscape: PreviewLayout = .fixed(width: 568, height: 320) 15 | static var iPhone11Landscape: PreviewLayout = .fixed(width: 896, height: 414) 16 | } 17 | -------------------------------------------------------------------------------- /04-integrating-swiftui/RGBBullsEye/RGBBullsEye/Reusables/TimeCounter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TimeCounter.swift 3 | // RGBBullsEye 4 | // 5 | // Created by Brian Sipple on 10/9/19. 6 | // Copyright © 2019 CypherPoet. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Combine 11 | 12 | 13 | final class TimeCounter: ObservableObject { 14 | let objectWillChange = PassthroughSubject() 15 | 16 | var timer: Timer? 17 | var counter = 0 18 | 19 | 20 | @objc func updateCounter() { 21 | counter += 1 22 | objectWillChange.send(self) 23 | } 24 | 25 | 26 | init() { 27 | timer = Timer.scheduledTimer( 28 | timeInterval: 1, 29 | target: self, 30 | selector: #selector(TimeCounter.updateCounter), 31 | userInfo: nil, 32 | repeats: true 33 | ) 34 | } 35 | 36 | 37 | func killTimer() { 38 | timer?.invalidate() 39 | timer = nil 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /04-integrating-swiftui/RGBBullsEye/Screenshots/recording-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/04-integrating-swiftui/RGBBullsEye/Screenshots/recording-1.gif -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus WatchKit App/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus WatchKit App/Base.lproj/Interface.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus WatchKit Extension/Assets.xcassets/Complication.complicationset/Circular.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "version" : 1, 26 | "author" : "xcode" 27 | } 28 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus WatchKit Extension/Assets.xcassets/Complication.complicationset/Extra Large.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "version" : 1, 26 | "author" : "xcode" 27 | } 28 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus WatchKit Extension/Assets.xcassets/Complication.complicationset/Graphic Bezel.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "version" : 1, 26 | "author" : "xcode" 27 | } 28 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus WatchKit Extension/Assets.xcassets/Complication.complicationset/Graphic Circular.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "version" : 1, 26 | "author" : "xcode" 27 | } 28 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus WatchKit Extension/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "version" : 1, 26 | "author" : "xcode" 27 | } 28 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus WatchKit Extension/Assets.xcassets/Complication.complicationset/Graphic Large Rectangular.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "version" : 1, 26 | "author" : "xcode" 27 | } 28 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus WatchKit Extension/Assets.xcassets/Complication.complicationset/Modular.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "version" : 1, 26 | "author" : "xcode" 27 | } 28 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus WatchKit Extension/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "version" : 1, 26 | "author" : "xcode" 27 | } 28 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus WatchKit Extension/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus WatchKit Extension/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/AppIcon.appiconset/Icon1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/AppIcon.appiconset/Icon1024.png -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/AppIcon.appiconset/Icon20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/AppIcon.appiconset/Icon20.png -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/AppIcon.appiconset/Icon20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/AppIcon.appiconset/Icon20@2x.png -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/AppIcon.appiconset/Icon29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/AppIcon.appiconset/Icon29.png -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/AppIcon.appiconset/Icon29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/AppIcon.appiconset/Icon29@2x.png -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/AppIcon.appiconset/Icon40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/AppIcon.appiconset/Icon40.png -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/AppIcon.appiconset/Icon40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/AppIcon.appiconset/Icon40@2x.png -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/AppIcon.appiconset/Icon76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/AppIcon.appiconset/Icon76.png -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/AppIcon.appiconset/Icon76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/AppIcon.appiconset/Icon76@2x.png -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/AppIcon.appiconset/Icon83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/AppIcon.appiconset/Icon83.5@2x.png -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/AppIcon.appiconset/icon-app-20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/AppIcon.appiconset/icon-app-20@2x.png -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/AppIcon.appiconset/icon-app-20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/AppIcon.appiconset/icon-app-20@3x.png -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/AppIcon.appiconset/icon-app-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/AppIcon.appiconset/icon-app-29@2x.png -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/AppIcon.appiconset/icon-app-29@3x copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/AppIcon.appiconset/icon-app-29@3x copy.png -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/AppIcon.appiconset/icon-app-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/AppIcon.appiconset/icon-app-40@2x.png -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/AppIcon.appiconset/icon-app-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/AppIcon.appiconset/icon-app-40@3x.png -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/AppIcon.appiconset/icon-app-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/AppIcon.appiconset/icon-app-60@2x.png -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/AppIcon.appiconset/icon-app-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/AppIcon.appiconset/icon-app-60@3x.png -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/colors/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/colors/rw-dark.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0x33", 13 | "alpha" : "1.000", 14 | "blue" : "0x33", 15 | "green" : "0x33" 16 | } 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/colors/rw-green.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0x00", 13 | "alpha" : "1.000", 14 | "blue" : "0x37", 15 | "green" : "0x68" 16 | } 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/colors/rw-light.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0xF2", 13 | "alpha" : "1.000", 14 | "blue" : "0xFA", 15 | "green" : "0xF6" 16 | } 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/launch-assets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/launch-assets/rw-logo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "Razewarelogo_1024.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/launch-assets/rw-logo.imageset/Razewarelogo_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/launch-assets/rw-logo.imageset/Razewarelogo_1024.png -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/launch-assets/rwdevcon-bg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "rwdevcon-bg.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/launch-assets/rwdevcon-bg.imageset/rwdevcon-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Assets.xcassets/launch-assets/rwdevcon-bg.imageset/rwdevcon-bg.png -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/BullsEyePlus.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Reusables/Extensions/UserDefaults+ObservableObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserDefaults+ObservableObject.swift 3 | // BullsEyePlus 4 | // 5 | // Created by CypherPoet on 10/13/19. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | 11 | extension UserDefaults: ObservableObject {} 12 | -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Settings.bundle/Root.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | StringsTable 6 | Root 7 | PreferenceSpecifiers 8 | 9 | 10 | Type 11 | PSToggleSwitchSpecifier 12 | Title 13 | Show Hint 14 | Key 15 | show_hint 16 | DefaultValue 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Settings.bundle/en.lproj/Root.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/05-the-apple-ecosystem/BullsEyePlus/BullsEyePlus/Settings.bundle/en.lproj/Root.strings -------------------------------------------------------------------------------- /05-the-apple-ecosystem/MacBullseye/MacBullseye/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/MacBullseye/MacBullseye/MacBullseye.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /05-the-apple-ecosystem/MacBullseye/MacBullseye/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/Packages/Game/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | -------------------------------------------------------------------------------- /05-the-apple-ecosystem/Packages/Game/README.md: -------------------------------------------------------------------------------- 1 | # Game 2 | 3 | A description of this package. 4 | -------------------------------------------------------------------------------- /05-the-apple-ecosystem/Packages/Game/Tests/GameTests/GameTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Game 3 | 4 | final class GameTests: XCTestCase { 5 | 6 | func testInit() { 7 | // This is an example of a functional test case. 8 | // Use XCTAssert and related functions to verify your tests produce the correct 9 | // results. 10 | XCTAssertNotNil(BullsEyeGame()) 11 | } 12 | 13 | static var allTests = [ 14 | ("testInit", testInit), 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /05-the-apple-ecosystem/Packages/Game/Tests/GameTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(GameTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /05-the-apple-ecosystem/Packages/Game/Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import GameTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += GameTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /05-the-apple-ecosystem/Packages/GameViewKit/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | -------------------------------------------------------------------------------- /05-the-apple-ecosystem/Packages/GameViewKit/README.md: -------------------------------------------------------------------------------- 1 | # GameView 2 | 3 | A description of this package. 4 | -------------------------------------------------------------------------------- /05-the-apple-ecosystem/Packages/GameViewKit/Tests/GameViewKitTests/GameViewKitTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import GameViewKit 3 | 4 | 5 | final class GameViewKitTests: XCTestCase { 6 | 7 | func testInit() { 8 | XCTAssertNotNil(GameViewKitTests()) 9 | } 10 | 11 | static var allTests = [ 12 | ("testInit", testInit), 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /05-the-apple-ecosystem/Packages/GameViewKit/Tests/GameViewKitTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(GameViewKitTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /05-the-apple-ecosystem/Packages/GameViewKit/Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import GameViewTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += GameViewKitTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /05-the-apple-ecosystem/RGBBullsEye/Design/book-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/05-the-apple-ecosystem/RGBBullsEye/Design/book-screenshot.png -------------------------------------------------------------------------------- /05-the-apple-ecosystem/RGBBullsEye/RGBBullsEye/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/RGBBullsEye/RGBBullsEye/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/RGBBullsEye/RGBBullsEye/Reusables/Extensions/PreviewLayout+Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreviewProvider+Utils.swift 3 | // RGBBullsEye 4 | // 5 | // Created by Brian Sipple on 10/7/19. 6 | // Copyright © 2019 CypherPoet. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | 13 | extension PreviewLayout { 14 | static var iPhoneSELandscape: PreviewLayout = .fixed(width: 568, height: 320) 15 | static var iPhone11Landscape: PreviewLayout = .fixed(width: 896, height: 414) 16 | } 17 | -------------------------------------------------------------------------------- /05-the-apple-ecosystem/RGBBullsEye/Screenshots/recording-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/05-the-apple-ecosystem/RGBBullsEye/Screenshots/recording-1.gif -------------------------------------------------------------------------------- /05-the-apple-ecosystem/TVBullseye/TVBullseye/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv" 5 | } 6 | ], 7 | "info" : { 8 | "version" : 1, 9 | "author" : "xcode" 10 | } 11 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/TVBullseye/TVBullseye/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/TVBullseye/TVBullseye/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "layers" : [ 3 | { 4 | "filename" : "Front.imagestacklayer" 5 | }, 6 | { 7 | "filename" : "Middle.imagestacklayer" 8 | }, 9 | { 10 | "filename" : "Back.imagestacklayer" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/TVBullseye/TVBullseye/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv" 5 | } 6 | ], 7 | "info" : { 8 | "version" : 1, 9 | "author" : "xcode" 10 | } 11 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/TVBullseye/TVBullseye/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/TVBullseye/TVBullseye/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv" 5 | } 6 | ], 7 | "info" : { 8 | "version" : 1, 9 | "author" : "xcode" 10 | } 11 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/TVBullseye/TVBullseye/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/TVBullseye/TVBullseye/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "tv", 9 | "scale" : "2x" 10 | } 11 | ], 12 | "info" : { 13 | "version" : 1, 14 | "author" : "xcode" 15 | } 16 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/TVBullseye/TVBullseye/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/TVBullseye/TVBullseye/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "layers" : [ 3 | { 4 | "filename" : "Front.imagestacklayer" 5 | }, 6 | { 7 | "filename" : "Middle.imagestacklayer" 8 | }, 9 | { 10 | "filename" : "Back.imagestacklayer" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/TVBullseye/TVBullseye/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "tv", 9 | "scale" : "2x" 10 | } 11 | ], 12 | "info" : { 13 | "version" : 1, 14 | "author" : "xcode" 15 | } 16 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/TVBullseye/TVBullseye/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/TVBullseye/TVBullseye/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "tv", 9 | "scale" : "2x" 10 | } 11 | ], 12 | "info" : { 13 | "version" : 1, 14 | "author" : "xcode" 15 | } 16 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/TVBullseye/TVBullseye/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/TVBullseye/TVBullseye/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "assets" : [ 3 | { 4 | "size" : "1280x768", 5 | "idiom" : "tv", 6 | "filename" : "App Icon - App Store.imagestack", 7 | "role" : "primary-app-icon" 8 | }, 9 | { 10 | "size" : "400x240", 11 | "idiom" : "tv", 12 | "filename" : "App Icon.imagestack", 13 | "role" : "primary-app-icon" 14 | }, 15 | { 16 | "size" : "2320x720", 17 | "idiom" : "tv", 18 | "filename" : "Top Shelf Image Wide.imageset", 19 | "role" : "top-shelf-image-wide" 20 | }, 21 | { 22 | "size" : "1920x720", 23 | "idiom" : "tv", 24 | "filename" : "Top Shelf Image.imageset", 25 | "role" : "top-shelf-image" 26 | } 27 | ], 28 | "info" : { 29 | "version" : 1, 30 | "author" : "xcode" 31 | } 32 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/TVBullseye/TVBullseye/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "tv", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "tv-marketing", 13 | "scale" : "1x" 14 | }, 15 | { 16 | "idiom" : "tv-marketing", 17 | "scale" : "2x" 18 | } 19 | ], 20 | "info" : { 21 | "version" : 1, 22 | "author" : "xcode" 23 | } 24 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/TVBullseye/TVBullseye/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "tv", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "tv-marketing", 13 | "scale" : "1x" 14 | }, 15 | { 16 | "idiom" : "tv-marketing", 17 | "scale" : "2x" 18 | } 19 | ], 20 | "info" : { 21 | "version" : 1, 22 | "author" : "xcode" 23 | } 24 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/TVBullseye/TVBullseye/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /05-the-apple-ecosystem/TVBullseye/TVBullseye/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 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIRequiredDeviceCapabilities 26 | 27 | arm64 28 | 29 | UIUserInterfaceStyle 30 | Automatic 31 | 32 | 33 | -------------------------------------------------------------------------------- /05-the-apple-ecosystem/TVBullseye/TVBullseye/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /06-intro-to-controls-text-and-image/Kuchi/Kuchi.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /06-intro-to-controls-text-and-image/Kuchi/Kuchi.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /06-intro-to-controls-text-and-image/Kuchi/Kuchi/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /06-intro-to-controls-text-and-image/Kuchi/Kuchi/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /06-intro-to-controls-text-and-image/Kuchi/Kuchi/Resources/Assets.xcassets/Images/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /06-intro-to-controls-text-and-image/Kuchi/Kuchi/Resources/Assets.xcassets/Images/swift_world.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "swift_world.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /06-intro-to-controls-text-and-image/Kuchi/Kuchi/Resources/Assets.xcassets/Images/swift_world.imageset/swift_world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/06-intro-to-controls-text-and-image/Kuchi/Kuchi/Resources/Assets.xcassets/Images/swift_world.imageset/swift_world.png -------------------------------------------------------------------------------- /06-intro-to-controls-text-and-image/Kuchi/Kuchi/Scenes/EntryView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EntryView.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/16/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct EntryView: View { 13 | private let userName: String 14 | private let isRegistered: Bool 15 | 16 | 17 | init( 18 | userName: String, 19 | isRegistered: Bool 20 | ) { 21 | self.userName = userName 22 | self.isRegistered = isRegistered 23 | } 24 | } 25 | 26 | 27 | extension EntryView { 28 | var body: some View { 29 | Group { 30 | if isRegistered { 31 | WelcomeView(userName: userName) 32 | } else { 33 | RegistrationView() 34 | } 35 | } 36 | } 37 | } 38 | 39 | 40 | struct EntryView_Previews: PreviewProvider { 41 | static var previews: some View { 42 | EntryView(userName: "CypherPoet", isRegistered: true) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /06-intro-to-controls-text-and-image/Kuchi/Kuchi/Scenes/Home/HomeView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomeView.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/19/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct HomeView: View { 13 | 14 | } 15 | 16 | 17 | // MARK: - Body 18 | extension HomeView { 19 | 20 | var body: some View { 21 | Text(/*@START_MENU_TOKEN@*/"Hello World!"/*@END_MENU_TOKEN@*/) 22 | } 23 | } 24 | 25 | 26 | // MARK: - Preview 27 | struct HomeView_Previews: PreviewProvider { 28 | static var previews: some View { 29 | HomeView() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /06-intro-to-controls-text-and-image/Kuchi/Resources/Images/swift_world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/06-intro-to-controls-text-and-image/Kuchi/Resources/Images/swift_world.png -------------------------------------------------------------------------------- /07-state-and-data-flow/Kuchi/Kuchi.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /07-state-and-data-flow/Kuchi/Kuchi.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /07-state-and-data-flow/Kuchi/Kuchi.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "CypherPoetCore", 6 | "repositoryURL": "https://github.com/CypherPoet/CypherPoetCore.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "1747e86c937c1950cc0907a49587098fcda00b45", 10 | "version": "0.0.17" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /07-state-and-data-flow/Kuchi/Kuchi/Data/Models/User+Profile.swift: -------------------------------------------------------------------------------- 1 | // 2 | // User+Profile.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/19/19. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | 13 | extension User { 14 | 15 | struct Profile { 16 | var name: String = "" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /07-state-and-data-flow/Kuchi/Kuchi/Data/Models/User.swift: -------------------------------------------------------------------------------- 1 | // 2 | // User.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/19/19. 6 | // ✌️ 7 | // 8 | 9 | import Combine 10 | 11 | 12 | final class User: ObservableObject { 13 | @Published var isRegistered: Bool = false 14 | @Published var profile = Profile() 15 | 16 | // 🤔 Not sure if its ideal to have a manual `willChange` instead of using 17 | // @Published, but this is the book example if we need to use it: 18 | 19 | // let willChange = PassthroughSubject() 20 | // 21 | // var profile = Profile() { 22 | // willSet { 23 | // // 📝 The idea is that a subscriber can receive the `User` instance 24 | // // whenever the `profile` changes (`@Published` would send the `profile`). 25 | // // 26 | // // Again... skeptical, but rolling with it for now 27 | // willChange.send(self) 28 | // } 29 | // } 30 | 31 | 32 | init(name: String) { 33 | self.profile = Profile(name: name) 34 | } 35 | } 36 | 37 | 38 | #if DEBUG 39 | 40 | let sampleUser = User(name: "CypherPoet") 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /07-state-and-data-flow/Kuchi/Kuchi/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /07-state-and-data-flow/Kuchi/Kuchi/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /07-state-and-data-flow/Kuchi/Kuchi/Resources/Assets.xcassets/Images/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /07-state-and-data-flow/Kuchi/Kuchi/Resources/Assets.xcassets/Images/swift_world.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "swift_world.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /07-state-and-data-flow/Kuchi/Kuchi/Resources/Assets.xcassets/Images/swift_world.imageset/swift_world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/07-state-and-data-flow/Kuchi/Kuchi/Resources/Assets.xcassets/Images/swift_world.imageset/swift_world.png -------------------------------------------------------------------------------- /07-state-and-data-flow/Kuchi/Kuchi/Scenes/EntryView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EntryView.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/16/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct EntryView: View { 13 | @EnvironmentObject var user: User 14 | } 15 | 16 | 17 | extension EntryView { 18 | var body: some View { 19 | Group { 20 | if user.isRegistered { 21 | WelcomeView() 22 | } else { 23 | RegistrationView() 24 | } 25 | } 26 | } 27 | } 28 | 29 | 30 | struct EntryView_Previews: PreviewProvider { 31 | static var previews: some View { 32 | EntryView() 33 | .environmentObject(sampleUser) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /07-state-and-data-flow/Kuchi/Kuchi/Scenes/Home/CongratulationsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CongratulationsView.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/21/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct CongratulationsView: View { 13 | } 14 | 15 | 16 | // MARK: - Body 17 | extension CongratulationsView { 18 | 19 | var body: some View { 20 | VStack { 21 | Text("🎉 Congratulations!") 22 | .font(.largeTitle) 23 | } 24 | } 25 | } 26 | 27 | 28 | // MARK: - Preview 29 | struct CongratulationsView_Previews: PreviewProvider { 30 | 31 | static var previews: some View { 32 | CongratulationsView() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /07-state-and-data-flow/Kuchi/Kuchi/Scenes/Home/ProgressView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProgressView.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/19/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct ProgressView: View { 13 | } 14 | 15 | 16 | // MARK: - Body 17 | extension ProgressView { 18 | 19 | var body: some View { 20 | Text(/*@START_MENU_TOKEN@*/"Hello World!"/*@END_MENU_TOKEN@*/) 21 | } 22 | } 23 | 24 | 25 | // MARK: - Preview 26 | struct ProgressView_Previews: PreviewProvider { 27 | static var previews: some View { 28 | ProgressView() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /07-state-and-data-flow/Kuchi/Kuchi/Scenes/Practice/ChoicesView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChoicesView.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/21/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct ChoicesView: View { 13 | let choices: [String] 14 | let onSelect: ((String) -> Void)? 15 | } 16 | 17 | 18 | // MARK: - Body 19 | extension ChoicesView { 20 | 21 | var body: some View { 22 | VStack { 23 | ForEach(choices, id: \.self) { choice in 24 | VStack { 25 | Button(action: { 26 | self.onSelect?(choice) 27 | }, label: { 28 | Text(choice) 29 | }) 30 | .font(.title) 31 | .padding() 32 | 33 | Divider() 34 | } 35 | } 36 | } 37 | } 38 | } 39 | 40 | 41 | // MARK: - Preview 42 | struct ChoicesView_Previews: PreviewProvider { 43 | 44 | static var previews: some View { 45 | ChoicesView( 46 | choices: ["Alpha", "Bravo", "Charilie"], 47 | onSelect: { _ in } 48 | ) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /07-state-and-data-flow/Kuchi/Kuchi/Scenes/Practice/QuestionView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QuestionView.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/22/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct QuestionView: View { 13 | var question: String 14 | } 15 | 16 | 17 | // MARK: - Body 18 | extension QuestionView { 19 | 20 | var body: some View { 21 | HStack { 22 | Text(question) 23 | .padding(.horizontal) 24 | .allowsTightening(true) 25 | .foregroundColor(.primary) 26 | .lineLimit(5) 27 | .multilineTextAlignment(.center) 28 | .animation(.spring()) 29 | } 30 | 31 | } 32 | } 33 | 34 | 35 | // MARK: - Preview 36 | struct QuestionView_Previews: PreviewProvider { 37 | 38 | static var previews: some View { 39 | QuestionView(question: "Question") 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /07-state-and-data-flow/Kuchi/Kuchi/Scenes/Practice/ScoreView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScoreView.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/22/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct ScoreView: View { 13 | let score: Int 14 | let maxPossibleScore: Int 15 | } 16 | 17 | 18 | // MARK: - Body 19 | extension ScoreView { 20 | 21 | var body: some View { 22 | HStack { 23 | Text("\(score)/\(maxPossibleScore)") 24 | .font(.caption) 25 | .padding(4) 26 | 27 | Spacer() 28 | } 29 | } 30 | } 31 | 32 | 33 | // MARK: - Preview 34 | struct ScoreView_Previews: PreviewProvider { 35 | 36 | static var previews: some View { 37 | ScoreView(score: 90, maxPossibleScore: 100) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /07-state-and-data-flow/Kuchi/Kuchi/Scenes/Profile/ProfileView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProfileView.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/19/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct ProfileView: View { 13 | } 14 | 15 | 16 | // MARK: - Body 17 | extension ProfileView { 18 | 19 | var body: some View { 20 | Text(/*@START_MENU_TOKEN@*/"Hello World!"/*@END_MENU_TOKEN@*/) 21 | } 22 | } 23 | 24 | 25 | // MARK: - Preview 26 | struct ProfileView_Previews: PreviewProvider { 27 | static var previews: some View { 28 | ProfileView() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /07-state-and-data-flow/Kuchi/Packages/LanguageLearning/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | -------------------------------------------------------------------------------- /07-state-and-data-flow/Kuchi/Packages/LanguageLearning/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /07-state-and-data-flow/Kuchi/Packages/LanguageLearning/README.md: -------------------------------------------------------------------------------- 1 | # LanguageLearning 2 | 3 | A description of this package. 4 | -------------------------------------------------------------------------------- /07-state-and-data-flow/Kuchi/Packages/LanguageLearning/Sources/Learning/WordCard.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import Combine 3 | 4 | import Languages 5 | 6 | /// Provides a word challenge to complete with an origin word and translation to use 7 | /// for assessment (from `TranslatedWord`) and flags to use for measuring progress. 8 | public struct WordCard { 9 | 10 | /// `TranslatedWord` to use for assessment (includes original word and translation). 11 | public let word: TranslatedWord 12 | 13 | /// Determines whether this challenge has been completed. 14 | public var completed: Bool = false 15 | 16 | /// Determines whether this challenge was completed successfully. 17 | public var succeeded: Bool = false 18 | 19 | /// Initializes a new `WordCard` from a given `TranslatedWord`. 20 | public init(from word: TranslatedWord) { 21 | self.word = word 22 | } 23 | } 24 | 25 | 26 | extension WordCard: Hashable { 27 | public static func == (lhs: WordCard, rhs: WordCard) -> Bool { 28 | lhs.word.original == rhs.word.original 29 | } 30 | 31 | public func hash(into hasher: inout Hasher) { 32 | word.original.hash(into: &hasher) 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /07-state-and-data-flow/Kuchi/Packages/LanguageLearning/Tests/LearningTests/LearningTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Learning 3 | 4 | final class LearningTests: XCTestCase { 5 | } 6 | -------------------------------------------------------------------------------- /07-state-and-data-flow/Kuchi/Packages/LanguageLearning/Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | 4 | var tests = [XCTestCaseEntry]() 5 | 6 | XCTMain(tests) 7 | -------------------------------------------------------------------------------- /07-state-and-data-flow/Kuchi/Packages/Languages/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | -------------------------------------------------------------------------------- /07-state-and-data-flow/Kuchi/Packages/Languages/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "Languages", 8 | products: [ 9 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 10 | .library( 11 | name: "Languages", 12 | targets: ["Languages"]), 13 | ], 14 | dependencies: [ 15 | // Dependencies declare other packages that this package depends on. 16 | // .package(url: /* package url */, from: "1.0.0"), 17 | ], 18 | targets: [ 19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 20 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 21 | .target( 22 | name: "Languages", 23 | dependencies: []), 24 | .testTarget( 25 | name: "LanguagesTests", 26 | dependencies: ["Languages"]), 27 | ] 28 | ) 29 | -------------------------------------------------------------------------------- /07-state-and-data-flow/Kuchi/Packages/Languages/README.md: -------------------------------------------------------------------------------- 1 | # Languages 2 | 3 | A description of this package. 4 | -------------------------------------------------------------------------------- /07-state-and-data-flow/Kuchi/Packages/Languages/Tests/LanguagesTests/LanguagesTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Languages 3 | 4 | final class LanguagesTests: XCTestCase { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /07-state-and-data-flow/Kuchi/Packages/Languages/Tests/LanguagesTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(LanguagesTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /07-state-and-data-flow/Kuchi/Packages/Languages/Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import LanguagesTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += LanguagesTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /07-state-and-data-flow/Kuchi/Resources/Images/swift_world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/07-state-and-data-flow/Kuchi/Resources/Images/swift_world.png -------------------------------------------------------------------------------- /07-state-and-data-flow/Kuchi/Resources/jp.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "original": "はい", "pronunciation": "hai", "translation": "yes" }, 3 | { "original": "いいえ", "pronunciation": "iie", "translation": "no" }, 4 | { "original": "おねがい します", "pronunciation": "onegai shimasu", "translation": "please" }, 5 | { "original": "こんにちわ", "pronunciation": "konnichiwa", "translation": "hello" }, 6 | { "original": "はじめまして", "pronunciation": "hajimemashite", "translation": "nice to meet you" }, 7 | { "original": "もしもし", "pronunciation": "moshi moshi", "translation": "hello" }, 8 | { "original": "すみません", "pronunciation": "sumimasen", "translation": "excuse me" }, 9 | { "original": "ありがとう", "pronunciation": "arigatō", "translation": "thank you" }, 10 | { "original": "ごめんなさい", "pronunciation": "gomenasai", "translation": "sorry" } 11 | ] 12 | -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Kuchi.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Kuchi.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Kuchi.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "CypherPoetCore", 6 | "repositoryURL": "https://github.com/CypherPoet/CypherPoetCore.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "d074a19fd4b6ecbb347b03522290e049fc5a1da6", 10 | "version": "0.0.18" 11 | } 12 | }, 13 | { 14 | "package": "CypherPoetSwiftUIKit", 15 | "repositoryURL": "https://github.com/CypherPoet/CypherPoetSwiftUIKit.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "45b7bed096f0cc77082dc2d4c762aecfd29a0717", 19 | "version": "0.0.13" 20 | } 21 | }, 22 | { 23 | "package": "CypherPoetUIKit", 24 | "repositoryURL": "https://github.com/CypherPoet/CypherPoetUIKit.git", 25 | "state": { 26 | "branch": null, 27 | "revision": "01e73704c47f32d59e0a37cd23974fd826071d76", 28 | "version": "0.0.8" 29 | } 30 | } 31 | ] 32 | }, 33 | "version": 1 34 | } 35 | -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Kuchi/Data/Models/UserProfile.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserProfile.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/19/19. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | 13 | struct UserProfile { 14 | var name: String? 15 | } 16 | 17 | 18 | extension UserProfile: Codable {} 19 | -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Kuchi/Data/Models/UserSettings.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserSettings.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/26/19. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | struct UserSettings { 13 | var shouldPersistProfile: Bool = true 14 | } 15 | 16 | 17 | extension UserSettings: Codable {} 18 | -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Kuchi/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Kuchi/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Kuchi/Resources/Assets.xcassets/Images/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Kuchi/Resources/Assets.xcassets/Images/swift_world.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "swift_world.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Kuchi/Resources/Assets.xcassets/Images/swift_world.imageset/swift_world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/08-controls-and-user-input/Kuchi/Kuchi/Resources/Assets.xcassets/Images/swift_world.imageset/swift_world.png -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Kuchi/Reusables/Extensions/Text+TextStyleWithViewModifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Text+TextStyleWithViewModifier.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/25/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | extension Text { 13 | 14 | /// Allows a `ViewModifier` instance to be passed to a `View`'s `textStyle` modifier 15 | /// Inspiration from https://mecid.github.io/2019/08/28/composable-styling-in-swiftui/ 16 | public func textStyle(style: Style) -> some View { 17 | ModifiedContent(content: self, modifier: style) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Kuchi/Reusables/Extensions/TextField+TextFieldStyleWithViewModifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextField+TextFieldStyleWithViewModifier.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/25/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | extension TextField { 13 | 14 | /// Allows a `ViewModifier` instance to be passed to a `View`'s `textFieldStyle` modifier 15 | /// Inspiration from https://mecid.github.io/2019/08/28/composable-styling-in-swiftui/ 16 | public func textFieldStyle(style: Style) -> some View { 17 | ModifiedContent(content: self, modifier: style) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Kuchi/Reusables/Styles/Buttons/CustomFilledButtonStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomFilledButtonStyle.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/27/19. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | 13 | struct CustomFilledButtonStyle: ButtonStyle { 14 | var fillColor: Color = .accentColor 15 | var foregroundColor: Color = .white 16 | var foregroundColorWhenPressed: Color = .gray 17 | 18 | 19 | func makeBody(configuration: Configuration) -> some View { 20 | configuration.label 21 | .frame(minWidth: 44, minHeight: 44) 22 | .padding(.horizontal, 14) 23 | .padding(.vertical, 6) 24 | .foregroundColor( 25 | configuration.isPressed ? foregroundColorWhenPressed : .white 26 | ) 27 | .background(fillColor) 28 | .cornerRadius(8) 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Kuchi/Reusables/Styles/Buttons/CustomOutlinedButtonStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomOutlinedButtonStyle.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/27/19. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | 13 | struct CustomOutlinedButtonStyle: ButtonStyle { 14 | let foregroundColor: Color = .accentColor 15 | let borderColor: Color = .accentColor 16 | 17 | 18 | func makeBody(configuration: Configuration) -> some View { 19 | configuration.label 20 | .frame(minWidth: 44, minHeight: 44) 21 | .padding() 22 | .foregroundColor(foregroundColor) 23 | .background( 24 | RoundedRectangle(cornerRadius: 8, style: .continuous) 25 | .stroke(borderColor) 26 | ) 27 | .border(Color.accentColor, width: 2) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Kuchi/Reusables/View Components/WelcomeMessageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WelcomeMessageView.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/24/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct WelcomeMessageView: View { 13 | } 14 | 15 | 16 | // MARK: - Body 17 | extension WelcomeMessageView { 18 | 19 | var body: some View { 20 | VStack(spacing: 4) { 21 | Text("Welcome to") 22 | .scaledSystemFont(size: 56, weight: .bold) 23 | .multilineTextAlignment(.leading) 24 | 25 | HStack { 26 | LogoImage() 27 | 28 | Text("Kuchi") 29 | .scaledSystemFont(size: 65, weight: .bold) 30 | .multilineTextAlignment(.trailing) 31 | } 32 | } 33 | .lineLimit(2) 34 | .fixedSize(horizontal: false, vertical: true) 35 | } 36 | } 37 | 38 | 39 | // MARK: - Preview 40 | struct WelcomeMessageView_Previews: PreviewProvider { 41 | 42 | static var previews: some View { 43 | WelcomeMessageView() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Kuchi/Reusables/View Modifiers/Text Field Styles/CustomRoundedTextFieldStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomRoundedTextFieldStyle.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/25/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct CustomRoundedTextFieldStyle: ViewModifier { 13 | 14 | func body(content: Content) -> some View { 15 | content 16 | .padding(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16)) 17 | .background(Color(UIColor.systemBackground)) 18 | .overlay( 19 | RoundedRectangle(cornerRadius: 12) 20 | .stroke(lineWidth: 2) 21 | .foregroundColor(.accentColor) 22 | ) 23 | .shadow(color: Color.gray.opacity(0.4), radius: 3, x: 1, y: 2) 24 | } 25 | } 26 | 27 | 28 | -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Kuchi/Reusables/Views/LogoImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LogoImage.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/24/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct LogoImage: View { 13 | } 14 | 15 | 16 | // MARK: - Body 17 | extension LogoImage { 18 | 19 | var body: some View { 20 | Image(systemName: "table") 21 | .resizable() 22 | .frame(width: 60, height: 60) 23 | .cornerRadius(60 / 2) 24 | .background(Color(white: 0.9)) 25 | .clipShape(Circle()) 26 | } 27 | } 28 | 29 | 30 | // MARK: - Preview 31 | struct LogoImage_Previews: PreviewProvider { 32 | 33 | static var previews: some View { 34 | LogoImage() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Kuchi/Reusables/Views/WelcomeBackgroundImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WelcomeBackgroundImage.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/24/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct WelcomeBackgroundImage: View { 13 | } 14 | 15 | 16 | // MARK: - Body 17 | extension WelcomeBackgroundImage { 18 | 19 | var body: some View { 20 | Image("swift_world") 21 | .resizable() 22 | .aspectRatio(1 / 1, contentMode: .fill) 23 | .edgesIgnoringSafeArea(.all) 24 | .saturation(0.5) 25 | .blur(radius: 5) 26 | .opacity(0.08) 27 | } 28 | } 29 | 30 | 31 | // MARK: - Preview 32 | struct WelcomeBackgroundImage_Previews: PreviewProvider { 33 | 34 | static var previews: some View { 35 | WelcomeBackgroundImage() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Kuchi/Scenes/EntryView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EntryView.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/16/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct EntryView: View { 13 | @EnvironmentObject var userStore: UserStore 14 | } 15 | 16 | 17 | extension EntryView { 18 | var body: some View { 19 | Group { 20 | if userStore.isRegistered { 21 | WelcomeView(username: userStore.profile.name!) 22 | } else { 23 | RegistrationView() 24 | } 25 | } 26 | } 27 | } 28 | 29 | 30 | struct EntryView_Previews: PreviewProvider { 31 | static var previews: some View { 32 | EntryView() 33 | .environmentObject(sampleUserStoreWithProfile) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Kuchi/Scenes/Home/CongratulationsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CongratulationsView.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/21/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct CongratulationsView: View { 13 | } 14 | 15 | 16 | // MARK: - Body 17 | extension CongratulationsView { 18 | 19 | var body: some View { 20 | VStack { 21 | Text("🎉 Congratulations!") 22 | .font(.largeTitle) 23 | } 24 | } 25 | } 26 | 27 | 28 | // MARK: - Preview 29 | struct CongratulationsView_Previews: PreviewProvider { 30 | 31 | static var previews: some View { 32 | CongratulationsView() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Kuchi/Scenes/Home/ProgressView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProgressView.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/19/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct ProgressView: View { 13 | } 14 | 15 | 16 | // MARK: - Body 17 | extension ProgressView { 18 | 19 | var body: some View { 20 | Text(/*@START_MENU_TOKEN@*/"Hello World!"/*@END_MENU_TOKEN@*/) 21 | } 22 | } 23 | 24 | 25 | // MARK: - Preview 26 | struct ProgressView_Previews: PreviewProvider { 27 | static var previews: some View { 28 | ProgressView() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Kuchi/Scenes/Practice/ChoicesView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChoicesView.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/21/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct ChoicesView: View { 13 | let choices: [String] 14 | let onSelect: ((String) -> Void)? 15 | } 16 | 17 | 18 | // MARK: - Body 19 | extension ChoicesView { 20 | 21 | var body: some View { 22 | VStack { 23 | ForEach(choices, id: \.self) { choice in 24 | VStack { 25 | Button(action: { 26 | self.onSelect?(choice) 27 | }, label: { 28 | Text(choice) 29 | }) 30 | .font(.title) 31 | .padding() 32 | 33 | Divider() 34 | } 35 | } 36 | } 37 | } 38 | } 39 | 40 | 41 | // MARK: - Preview 42 | struct ChoicesView_Previews: PreviewProvider { 43 | 44 | static var previews: some View { 45 | ChoicesView( 46 | choices: ["Alpha", "Bravo", "Charilie"], 47 | onSelect: { _ in } 48 | ) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Kuchi/Scenes/Practice/QuestionView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QuestionView.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/22/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct QuestionView: View { 13 | var question: String 14 | } 15 | 16 | 17 | // MARK: - Body 18 | extension QuestionView { 19 | 20 | var body: some View { 21 | HStack { 22 | Text(question) 23 | .padding(.horizontal) 24 | .allowsTightening(true) 25 | .foregroundColor(.primary) 26 | .lineLimit(5) 27 | .multilineTextAlignment(.center) 28 | .animation(.spring()) 29 | } 30 | 31 | } 32 | } 33 | 34 | 35 | // MARK: - Preview 36 | struct QuestionView_Previews: PreviewProvider { 37 | 38 | static var previews: some View { 39 | QuestionView(question: "Question") 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Kuchi/Scenes/Practice/ScoreView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScoreView.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/22/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct ScoreView: View { 13 | let score: Int 14 | let maxPossibleScore: Int 15 | } 16 | 17 | 18 | // MARK: - Body 19 | extension ScoreView { 20 | 21 | var body: some View { 22 | HStack { 23 | Text("\(score)/\(maxPossibleScore)") 24 | .font(.caption) 25 | .padding(4) 26 | 27 | Spacer() 28 | } 29 | } 30 | } 31 | 32 | 33 | // MARK: - Preview 34 | struct ScoreView_Previews: PreviewProvider { 35 | 36 | static var previews: some View { 37 | ScoreView(score: 90, maxPossibleScore: 100) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Kuchi/Scenes/Profile/ProfileView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProfileView.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/19/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct ProfileView: View { 13 | } 14 | 15 | 16 | // MARK: - Body 17 | extension ProfileView { 18 | 19 | var body: some View { 20 | Text(/*@START_MENU_TOKEN@*/"Hello World!"/*@END_MENU_TOKEN@*/) 21 | } 22 | } 23 | 24 | 25 | // MARK: - Preview 26 | struct ProfileView_Previews: PreviewProvider { 27 | static var previews: some View { 28 | ProfileView() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Packages/LanguageLearning/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Packages/LanguageLearning/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Packages/LanguageLearning/README.md: -------------------------------------------------------------------------------- 1 | # LanguageLearning 2 | 3 | A description of this package. 4 | -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Packages/LanguageLearning/Sources/Learning/WordCard.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import Combine 3 | 4 | import Languages 5 | 6 | /// Provides a word challenge to complete with an origin word and translation to use 7 | /// for assessment (from `TranslatedWord`) and flags to use for measuring progress. 8 | public struct WordCard { 9 | 10 | /// `TranslatedWord` to use for assessment (includes original word and translation). 11 | public let word: TranslatedWord 12 | 13 | /// Determines whether this challenge has been completed. 14 | public var completed: Bool = false 15 | 16 | /// Determines whether this challenge was completed successfully. 17 | public var succeeded: Bool = false 18 | 19 | /// Initializes a new `WordCard` from a given `TranslatedWord`. 20 | public init(from word: TranslatedWord) { 21 | self.word = word 22 | } 23 | } 24 | 25 | 26 | extension WordCard: Hashable { 27 | public static func == (lhs: WordCard, rhs: WordCard) -> Bool { 28 | lhs.word.original == rhs.word.original 29 | } 30 | 31 | public func hash(into hasher: inout Hasher) { 32 | word.original.hash(into: &hasher) 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Packages/LanguageLearning/Tests/LearningTests/LearningTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Learning 3 | 4 | final class LearningTests: XCTestCase { 5 | } 6 | -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Packages/LanguageLearning/Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | 4 | var tests = [XCTestCaseEntry]() 5 | 6 | XCTMain(tests) 7 | -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Packages/Languages/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Packages/Languages/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "Languages", 8 | products: [ 9 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 10 | .library( 11 | name: "Languages", 12 | targets: ["Languages"]), 13 | ], 14 | dependencies: [ 15 | // Dependencies declare other packages that this package depends on. 16 | // .package(url: /* package url */, from: "1.0.0"), 17 | ], 18 | targets: [ 19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 20 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 21 | .target( 22 | name: "Languages", 23 | dependencies: []), 24 | .testTarget( 25 | name: "LanguagesTests", 26 | dependencies: ["Languages"]), 27 | ] 28 | ) 29 | -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Packages/Languages/README.md: -------------------------------------------------------------------------------- 1 | # Languages 2 | 3 | A description of this package. 4 | -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Packages/Languages/Tests/LanguagesTests/LanguagesTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Languages 3 | 4 | final class LanguagesTests: XCTestCase { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Packages/Languages/Tests/LanguagesTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(LanguagesTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Packages/Languages/Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import LanguagesTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += LanguagesTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Resources/Images/swift_world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/08-controls-and-user-input/Kuchi/Resources/Images/swift_world.png -------------------------------------------------------------------------------- /08-controls-and-user-input/Kuchi/Resources/jp.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "original": "はい", "pronunciation": "hai", "translation": "yes" }, 3 | { "original": "いいえ", "pronunciation": "iie", "translation": "no" }, 4 | { "original": "おねがい します", "pronunciation": "onegai shimasu", "translation": "please" }, 5 | { "original": "こんにちわ", "pronunciation": "konnichiwa", "translation": "hello" }, 6 | { "original": "はじめまして", "pronunciation": "hajimemashite", "translation": "nice to meet you" }, 7 | { "original": "もしもし", "pronunciation": "moshi moshi", "translation": "hello" }, 8 | { "original": "すみません", "pronunciation": "sumimasen", "translation": "excuse me" }, 9 | { "original": "ありがとう", "pronunciation": "arigatō", "translation": "thank you" }, 10 | { "original": "ごめんなさい", "pronunciation": "gomenasai", "translation": "sorry" } 11 | ] 12 | -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Kuchi.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Kuchi.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Kuchi.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "CypherPoetCore", 6 | "repositoryURL": "https://github.com/CypherPoet/CypherPoetCore.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "d074a19fd4b6ecbb347b03522290e049fc5a1da6", 10 | "version": "0.0.18" 11 | } 12 | }, 13 | { 14 | "package": "CypherPoetSwiftUIKit", 15 | "repositoryURL": "https://github.com/CypherPoet/CypherPoetSwiftUIKit.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "cfe7b0435a175503de67f855ba6bb5c79cf6f297", 19 | "version": "0.0.21" 20 | } 21 | }, 22 | { 23 | "package": "CypherPoetUIKit", 24 | "repositoryURL": "https://github.com/CypherPoet/CypherPoetUIKit.git", 25 | "state": { 26 | "branch": null, 27 | "revision": "2e943efd0fd95884880203b8747ba0b98e66e8b7", 28 | "version": "0.0.9" 29 | } 30 | } 31 | ] 32 | }, 33 | "version": 1 34 | } 35 | -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Kuchi/Data/Models/User.swift: -------------------------------------------------------------------------------- 1 | // 2 | // User.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/29/19. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | 13 | struct User { 14 | var isRegistered = false 15 | // var profile = UserProfile() 16 | 17 | // init() {} 18 | // 19 | // init(name: String) { 20 | // self.profile = UserProfile(name: name) 21 | // } 22 | } 23 | 24 | 25 | // MARK: - Computeds 26 | extension User { 27 | // var isRegistered: Bool { profile.name != nil } 28 | } 29 | 30 | 31 | 32 | 33 | extension User: Codable {} 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Kuchi/Data/Models/UserProfile.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserProfile.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/19/19. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | 13 | struct UserProfile { 14 | var name: String? 15 | } 16 | 17 | 18 | extension UserProfile: Codable {} 19 | -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Kuchi/Data/State/AppState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppState.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/29/19. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | import CypherPoetSwiftUIKit_DataFlowUtils 11 | 12 | 13 | struct AppState { 14 | var userState = UserState() 15 | var userProfileState = UserProfileState() 16 | var settingsState = SettingsState() 17 | } 18 | 19 | 20 | 21 | enum AppAction { 22 | case userAction(_ action: UserAction) 23 | case userProfileAction(_ action: UserProfileAction) 24 | } 25 | 26 | 27 | 28 | // MARK: - Reducer 29 | let appReducer: Reducer = Reducer(reduce: { appState, action in 30 | switch action { 31 | case let .userAction(action): 32 | userReducer.reduce(&appState.userState, action) 33 | case let .userProfileAction(action): 34 | userProfileReducer.reduce(&appState.userProfileState, action) 35 | } 36 | }) 37 | -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Kuchi/Data/State/SettingsState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsState.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/29/19. 6 | // ✌️ 7 | // 8 | 9 | import Combine 10 | import CypherPoetCore_PropertyWrappers 11 | import CypherPoetSwiftUIKit_DataFlowUtils 12 | 13 | 14 | struct SettingsState { 15 | 16 | @UserDefault(UserDefaultsKey.shouldPersistProfile, defaultValue: true) 17 | var shouldPersistProfile: Bool 18 | 19 | } 20 | 21 | -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Kuchi/Data/State/UserState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserState.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/19/19. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | import Combine 11 | import SwiftUI 12 | import CypherPoetSwiftUIKit_DataFlowUtils 13 | 14 | 15 | 16 | struct UserState { 17 | @EnvironmentObject private var store: Store 18 | 19 | var user = User() 20 | } 21 | 22 | 23 | 24 | // MARK: - Computeds 25 | extension UserState { 26 | var isRegistered: Bool { user.isRegistered } 27 | var settingsState: SettingsState { store.state.settingsState } 28 | } 29 | 30 | 31 | 32 | // MARK: - Public Methods 33 | extension UserState { 34 | } 35 | 36 | 37 | // MARK: - Private Helpers 38 | private extension UserState { 39 | 40 | } 41 | 42 | 43 | 44 | enum UserAction { 45 | case save(_ user: User) 46 | case register 47 | } 48 | 49 | 50 | 51 | // MARK: - Reducer 52 | let userReducer: Reducer = Reducer(reduce: { userState, action in 53 | switch action { 54 | case let .save(user): 55 | userState.user = user 56 | case .register: 57 | userState.user.isRegistered = true 58 | } 59 | }) 60 | 61 | 62 | -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Kuchi/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Kuchi/Preview Content/Sample Data/ChallengeWords.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChallengeWords.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/31/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Kuchi/Preview Content/Sample Data/Samples.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Samples.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/30/19. 6 | // ✌️ 7 | // 8 | 9 | #if DEBUG 10 | 11 | import Foundation 12 | import CypherPoetSwiftUIKit_DataFlowUtils 13 | 14 | 15 | enum SampleAppState { 16 | static let empty = AppState() 17 | 18 | static let withUserProfile = AppState( 19 | userProfileState: UserProfileState( 20 | profile: UserProfile(name: "CypherPoet") 21 | ) 22 | ) 23 | } 24 | 25 | 26 | enum SampleUserState { 27 | static let `default` = UserState() 28 | } 29 | 30 | 31 | enum SampleSettingsState { 32 | static let `default` = SettingsState() 33 | } 34 | 35 | 36 | enum SampleUser { 37 | static let `default` = User() 38 | } 39 | 40 | 41 | enum SampleStore { 42 | 43 | static let empty: Store = { 44 | Store(initialState: SampleAppState.empty, appReducer: appReducer) 45 | }() 46 | 47 | 48 | static let withUserProfile: Store = { 49 | Store(initialState: SampleAppState.withUserProfile, appReducer: appReducer) 50 | }() 51 | } 52 | 53 | #endif 54 | 55 | -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Kuchi/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Kuchi/Resources/Assets.xcassets/Images/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Kuchi/Resources/Assets.xcassets/Images/swift_world.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "swift_world.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Kuchi/Resources/Assets.xcassets/Images/swift_world.imageset/swift_world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/09-stacks-and-containers/Kuchi/Kuchi/Resources/Assets.xcassets/Images/swift_world.imageset/swift_world.png -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Kuchi/Reusables/Constants/UserDefaultsKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserDefaultsKey.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/29/19. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | enum UserDefaultsKey { 13 | static let shouldPersistProfile = "should_persist_profile" 14 | } 15 | -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Kuchi/Reusables/Extensions/Text+TextStyleWithViewModifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Text+TextStyleWithViewModifier.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/25/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | extension Text { 13 | 14 | /// Allows a `ViewModifier` instance to be passed to a `View`'s `textStyle` modifier 15 | /// Inspiration from https://mecid.github.io/2019/08/28/composable-styling-in-swiftui/ 16 | public func textStyle(style: Style) -> some View { 17 | ModifiedContent(content: self, modifier: style) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Kuchi/Reusables/Extensions/TextField+TextFieldStyleWithViewModifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextField+TextFieldStyleWithViewModifier.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/25/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | extension TextField { 13 | 14 | /// Allows a `ViewModifier` instance to be passed to a `View`'s `textFieldStyle` modifier 15 | /// Inspiration from https://mecid.github.io/2019/08/28/composable-styling-in-swiftui/ 16 | public func textFieldStyle(style: Style) -> some View { 17 | ModifiedContent(content: self, modifier: style) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Kuchi/Reusables/View Components/WelcomeMessageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WelcomeMessageView.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/24/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct WelcomeMessageView: View { 13 | } 14 | 15 | 16 | // MARK: - Body 17 | extension WelcomeMessageView { 18 | 19 | var body: some View { 20 | VStack(spacing: 4) { 21 | Text("Welcome to") 22 | .scaledSystemFont(size: 56, weight: .bold) 23 | .multilineTextAlignment(.leading) 24 | 25 | HStack { 26 | LogoImage() 27 | 28 | Text("Kuchi") 29 | .scaledSystemFont(size: 65, weight: .bold) 30 | .multilineTextAlignment(.trailing) 31 | } 32 | } 33 | .lineLimit(2) 34 | .fixedSize(horizontal: false, vertical: true) 35 | } 36 | } 37 | 38 | 39 | // MARK: - Preview 40 | struct WelcomeMessageView_Previews: PreviewProvider { 41 | 42 | static var previews: some View { 43 | WelcomeMessageView() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Kuchi/Reusables/View Modifiers/Text Field Styles/CustomRoundedTextFieldStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomRoundedTextFieldStyle.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/25/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct CustomRoundedTextFieldStyle: ViewModifier { 13 | 14 | func body(content: Content) -> some View { 15 | content 16 | .padding(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16)) 17 | .background(Color(UIColor.systemBackground)) 18 | .overlay( 19 | RoundedRectangle(cornerRadius: 12) 20 | .stroke(lineWidth: 2) 21 | .foregroundColor(.accentColor) 22 | ) 23 | .shadow(color: Color.gray.opacity(0.4), radius: 3, x: 1, y: 2) 24 | } 25 | } 26 | 27 | 28 | -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Kuchi/Reusables/Views/LogoImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LogoImage.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/24/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct LogoImage: View { 13 | } 14 | 15 | 16 | // MARK: - Body 17 | extension LogoImage { 18 | 19 | var body: some View { 20 | Image(systemName: "table") 21 | .resizable() 22 | .frame(width: 60, height: 60) 23 | .cornerRadius(60 / 2) 24 | .background(Color(white: 0.9)) 25 | .clipShape(Circle()) 26 | } 27 | } 28 | 29 | 30 | // MARK: - Preview 31 | struct LogoImage_Previews: PreviewProvider { 32 | 33 | static var previews: some View { 34 | LogoImage() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Kuchi/Reusables/Views/WelcomeBackgroundImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WelcomeBackgroundImage.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/24/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct WelcomeBackgroundImage: View { 13 | } 14 | 15 | 16 | // MARK: - Body 17 | extension WelcomeBackgroundImage { 18 | 19 | var body: some View { 20 | Image("swift_world") 21 | .resizable() 22 | .aspectRatio(1 / 1, contentMode: .fill) 23 | .edgesIgnoringSafeArea(.all) 24 | .saturation(0.5) 25 | .blur(radius: 5) 26 | .opacity(0.08) 27 | } 28 | } 29 | 30 | 31 | // MARK: - Preview 32 | struct WelcomeBackgroundImage_Previews: PreviewProvider { 33 | 34 | static var previews: some View { 35 | WelcomeBackgroundImage() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Kuchi/Scenes/EntryView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EntryView.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/16/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | import CypherPoetSwiftUIKit_DataFlowUtils 11 | 12 | 13 | struct EntryView: View { 14 | @EnvironmentObject private var store: Store 15 | } 16 | 17 | 18 | extension EntryView { 19 | var isRegistered: Bool { store.state.userState.isRegistered } 20 | } 21 | 22 | extension EntryView { 23 | var body: some View { 24 | Group { 25 | if isRegistered { 26 | WelcomeView() 27 | } else { 28 | RegistrationView() 29 | } 30 | } 31 | } 32 | } 33 | 34 | 35 | struct EntryView_Previews: PreviewProvider { 36 | static var previews: some View { 37 | EntryView() 38 | .environmentObject(SampleStore.empty) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Kuchi/Scenes/Home/ProgressView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProgressView.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/19/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct ProgressView: View { 13 | } 14 | 15 | 16 | // MARK: - Body 17 | extension ProgressView { 18 | 19 | var body: some View { 20 | Text(/*@START_MENU_TOKEN@*/"Hello World!"/*@END_MENU_TOKEN@*/) 21 | } 22 | } 23 | 24 | 25 | // MARK: - Preview 26 | struct ProgressView_Previews: PreviewProvider { 27 | static var previews: some View { 28 | ProgressView() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Kuchi/Scenes/Practice/ChoicesView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChoicesView.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/21/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct ChoicesView: View { 13 | let choices: [String] 14 | let onSelect: ((String) -> Void)? 15 | } 16 | 17 | 18 | // MARK: - Body 19 | extension ChoicesView { 20 | 21 | var body: some View { 22 | VStack { 23 | ForEach(choices, id: \.self) { choice in 24 | VStack { 25 | Button(action: { 26 | self.onSelect?(choice) 27 | }, label: { 28 | Text(choice) 29 | }) 30 | .font(.title) 31 | .padding() 32 | 33 | Divider() 34 | } 35 | } 36 | } 37 | } 38 | } 39 | 40 | 41 | // MARK: - Preview 42 | struct ChoicesView_Previews: PreviewProvider { 43 | 44 | static var previews: some View { 45 | ChoicesView( 46 | choices: ["Alpha", "Bravo", "Charilie"], 47 | onSelect: { _ in } 48 | ) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Kuchi/Scenes/Practice/QuestionView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QuestionView.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/22/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct QuestionView: View { 13 | var question: String 14 | } 15 | 16 | 17 | // MARK: - Body 18 | extension QuestionView { 19 | 20 | var body: some View { 21 | HStack { 22 | Text(question) 23 | .scaledSystemFont(size: 64) 24 | .allowsTightening(true) 25 | .padding(.horizontal) 26 | .allowsTightening(true) 27 | .foregroundColor(.pink) 28 | .lineLimit(5) 29 | .multilineTextAlignment(.center) 30 | .animation(.spring()) 31 | } 32 | } 33 | } 34 | 35 | 36 | // MARK: - Preview 37 | struct QuestionView_Previews: PreviewProvider { 38 | 39 | static var previews: some View { 40 | QuestionView(question: "ごめんなさい") 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Kuchi/Scenes/Practice/ScoreView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScoreView.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/22/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct ScoreView: View { 13 | let score: Int 14 | let maxPossibleScore: Int 15 | } 16 | 17 | 18 | // MARK: - Body 19 | extension ScoreView { 20 | 21 | var body: some View { 22 | HStack { 23 | Text("\(score)/\(maxPossibleScore)") 24 | .font(.caption) 25 | .padding(4) 26 | 27 | Spacer() 28 | } 29 | } 30 | } 31 | 32 | 33 | // MARK: - Preview 34 | struct ScoreView_Previews: PreviewProvider { 35 | 36 | static var previews: some View { 37 | ScoreView(score: 90, maxPossibleScore: 100) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Kuchi/Scenes/Profile/ProfileView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProfileView.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/19/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct ProfileView: View { 13 | } 14 | 15 | 16 | // MARK: - Body 17 | extension ProfileView { 18 | 19 | var body: some View { 20 | Text(/*@START_MENU_TOKEN@*/"Hello World!"/*@END_MENU_TOKEN@*/) 21 | } 22 | } 23 | 24 | 25 | // MARK: - Preview 26 | struct ProfileView_Previews: PreviewProvider { 27 | static var previews: some View { 28 | ProfileView() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Packages/LanguageLearning/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Packages/LanguageLearning/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Packages/LanguageLearning/README.md: -------------------------------------------------------------------------------- 1 | # LanguageLearning 2 | 3 | A description of this package. 4 | -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Packages/LanguageLearning/Sources/Learning/WordCard.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import Combine 3 | 4 | import Languages 5 | 6 | /// Provides a word challenge to complete with an origin word and translation to use 7 | /// for assessment (from `TranslatedWord`) and flags to use for measuring progress. 8 | public struct WordCard { 9 | 10 | /// `TranslatedWord` to use for assessment (includes original word and translation). 11 | public let word: TranslatedWord 12 | 13 | /// Determines whether this challenge has been completed. 14 | public var completed: Bool = false 15 | 16 | /// Determines whether this challenge was completed successfully. 17 | public var succeeded: Bool = false 18 | 19 | /// Initializes a new `WordCard` from a given `TranslatedWord`. 20 | public init(from word: TranslatedWord) { 21 | self.word = word 22 | } 23 | } 24 | 25 | 26 | extension WordCard: Hashable { 27 | public static func == (lhs: WordCard, rhs: WordCard) -> Bool { 28 | lhs.word.original == rhs.word.original 29 | } 30 | 31 | public func hash(into hasher: inout Hasher) { 32 | word.original.hash(into: &hasher) 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Packages/LanguageLearning/Tests/LearningTests/LearningTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Learning 3 | 4 | final class LearningTests: XCTestCase { 5 | } 6 | -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Packages/LanguageLearning/Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | 4 | var tests = [XCTestCaseEntry]() 5 | 6 | XCTMain(tests) 7 | -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Packages/Languages/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Packages/Languages/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "Languages", 8 | products: [ 9 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 10 | .library( 11 | name: "Languages", 12 | targets: ["Languages"]), 13 | ], 14 | dependencies: [ 15 | // Dependencies declare other packages that this package depends on. 16 | // .package(url: /* package url */, from: "1.0.0"), 17 | ], 18 | targets: [ 19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 20 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 21 | .target( 22 | name: "Languages", 23 | dependencies: []), 24 | .testTarget( 25 | name: "LanguagesTests", 26 | dependencies: ["Languages"]), 27 | ] 28 | ) 29 | -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Packages/Languages/README.md: -------------------------------------------------------------------------------- 1 | # Languages 2 | 3 | A description of this package. 4 | -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Packages/Languages/Tests/LanguagesTests/LanguagesTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Languages 3 | 4 | final class LanguagesTests: XCTestCase { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Packages/Languages/Tests/LanguagesTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(LanguagesTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Packages/Languages/Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import LanguagesTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += LanguagesTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Resources/Images/swift_world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/09-stacks-and-containers/Kuchi/Resources/Images/swift_world.png -------------------------------------------------------------------------------- /09-stacks-and-containers/Kuchi/Resources/jp.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "original": "はい", "pronunciation": "hai", "translation": "yes" }, 3 | { "original": "いいえ", "pronunciation": "iie", "translation": "no" }, 4 | { "original": "おねがい します", "pronunciation": "onegai shimasu", "translation": "please" }, 5 | { "original": "こんにちわ", "pronunciation": "konnichiwa", "translation": "hello" }, 6 | { "original": "はじめまして", "pronunciation": "hajimemashite", "translation": "nice to meet you" }, 7 | { "original": "もしもし", "pronunciation": "moshi moshi", "translation": "hello" }, 8 | { "original": "すみません", "pronunciation": "sumimasen", "translation": "excuse me" }, 9 | { "original": "ありがとう", "pronunciation": "arigatō", "translation": "thank you" }, 10 | { "original": "ごめんなさい", "pronunciation": "gomenasai", "translation": "sorry" } 11 | ] 12 | -------------------------------------------------------------------------------- /10-lists-and-navigation/MountainAirport/MountainAirport.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /10-lists-and-navigation/MountainAirport/MountainAirport.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /10-lists-and-navigation/MountainAirport/MountainAirport.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "CypherPoetSwiftUIKit", 6 | "repositoryURL": "https://github.com/CypherPoet/CypherPoetSwiftUIKit.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "cfe7b0435a175503de67f855ba6bb5c79cf6f297", 10 | "version": "0.0.21" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /10-lists-and-navigation/MountainAirport/MountainAirport/Data/Constants/FlightDirection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlightDirection.swift 3 | // MountainAirport 4 | // 5 | // Created by CypherPoet on 11/1/19. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | public enum FlightDirection { 13 | case arrival 14 | case departure 15 | } 16 | 17 | 18 | extension FlightDirection { 19 | 20 | var boardName: String { 21 | switch self { 22 | case .arrival: 23 | return "Arrivals" 24 | case .departure: 25 | return "Departures" 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /10-lists-and-navigation/MountainAirport/MountainAirport/Data/Constants/FlightStatus.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlightStatus.swift 3 | // MountainAirport 4 | // 5 | // Created by CypherPoet on 11/1/19. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | public enum FlightStatus: String, CaseIterable { 13 | case onTime 14 | case delayed 15 | case cancelled 16 | case landed 17 | case departed 18 | } 19 | 20 | 21 | extension FlightStatus { 22 | 23 | var displayName: String { 24 | switch self { 25 | case .onTime: 26 | return "On Time" 27 | case .delayed: 28 | return "Delayed" 29 | case .cancelled: 30 | return "Cancelled" 31 | case .landed: 32 | return "Landed" 33 | case .departed: 34 | return "Departed" 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /10-lists-and-navigation/MountainAirport/MountainAirport/Data/State/AppState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppState.swift 3 | // MountainAirport 4 | // 5 | // Created by CypherPoet on 11/1/19. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | import CypherPoetSwiftUIKit_DataFlowUtils 11 | 12 | 13 | struct AppState { 14 | var flightInformationState = FlightInformationState() 15 | } 16 | 17 | 18 | enum AppAction { 19 | case flightInfo(_ action: FlightInformationAction) 20 | } 21 | 22 | 23 | // MARK: - Reducer 24 | let appReducer = Reducer { appState, action in 25 | switch action { 26 | case let .flightInfo(action): 27 | flightInformationReducer.reduce(&appState.flightInformationState, action) 28 | } 29 | } 30 | 31 | typealias AppStore = Store 32 | -------------------------------------------------------------------------------- /10-lists-and-navigation/MountainAirport/MountainAirport/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /10-lists-and-navigation/MountainAirport/MountainAirport/Preview Content/Sample Data/SampleData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SampleData.swift 3 | // MountainAirport 4 | // 5 | // Created by CypherPoet on 11/1/19. 6 | // ✌️ 7 | // 8 | 9 | #if DEBUG 10 | 11 | import Foundation 12 | 13 | enum SampleFlights { 14 | static let `default` = FlightInformation.generateFlights() 15 | } 16 | 17 | 18 | enum SampleFlightInformationState { 19 | static let `default` = FlightInformationState(flightInfo: SampleFlights.default) 20 | } 21 | 22 | 23 | enum SampleAppState { 24 | static let `default` = AppState(flightInformationState: SampleFlightInformationState.default) 25 | } 26 | 27 | 28 | enum SampleStore { 29 | static let `default` = AppStore(initialState: SampleAppState.default, appReducer: appReducer) 30 | } 31 | 32 | 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /10-lists-and-navigation/MountainAirport/MountainAirport/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/AppIcon.appiconset/Icon1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/AppIcon.appiconset/Icon1024.png -------------------------------------------------------------------------------- /11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/AppIcon.appiconset/Icon20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/AppIcon.appiconset/Icon20.png -------------------------------------------------------------------------------- /11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/AppIcon.appiconset/Icon20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/AppIcon.appiconset/Icon20@2x.png -------------------------------------------------------------------------------- /11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/AppIcon.appiconset/Icon29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/AppIcon.appiconset/Icon29.png -------------------------------------------------------------------------------- /11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/AppIcon.appiconset/Icon29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/AppIcon.appiconset/Icon29@2x.png -------------------------------------------------------------------------------- /11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/AppIcon.appiconset/Icon40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/AppIcon.appiconset/Icon40.png -------------------------------------------------------------------------------- /11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/AppIcon.appiconset/Icon40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/AppIcon.appiconset/Icon40@2x.png -------------------------------------------------------------------------------- /11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/AppIcon.appiconset/Icon76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/AppIcon.appiconset/Icon76.png -------------------------------------------------------------------------------- /11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/AppIcon.appiconset/Icon76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/AppIcon.appiconset/Icon76@2x.png -------------------------------------------------------------------------------- /11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/AppIcon.appiconset/Icon83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/AppIcon.appiconset/Icon83.5@2x.png -------------------------------------------------------------------------------- /11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/AppIcon.appiconset/icon-app-20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/AppIcon.appiconset/icon-app-20@2x.png -------------------------------------------------------------------------------- /11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/AppIcon.appiconset/icon-app-20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/AppIcon.appiconset/icon-app-20@3x.png -------------------------------------------------------------------------------- /11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/AppIcon.appiconset/icon-app-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/AppIcon.appiconset/icon-app-29@2x.png -------------------------------------------------------------------------------- /11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/AppIcon.appiconset/icon-app-29@3x copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/AppIcon.appiconset/icon-app-29@3x copy.png -------------------------------------------------------------------------------- /11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/AppIcon.appiconset/icon-app-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/AppIcon.appiconset/icon-app-40@2x.png -------------------------------------------------------------------------------- /11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/AppIcon.appiconset/icon-app-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/AppIcon.appiconset/icon-app-40@3x.png -------------------------------------------------------------------------------- /11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/AppIcon.appiconset/icon-app-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/AppIcon.appiconset/icon-app-60@2x.png -------------------------------------------------------------------------------- /11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/AppIcon.appiconset/icon-app-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/AppIcon.appiconset/icon-app-60@3x.png -------------------------------------------------------------------------------- /11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/colors/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/colors/rw-dark.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0x33", 13 | "alpha" : "1.000", 14 | "blue" : "0x33", 15 | "green" : "0x33" 16 | } 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/colors/rw-green.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0x00", 13 | "alpha" : "1.000", 14 | "blue" : "0x37", 15 | "green" : "0x68" 16 | } 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/colors/rw-light.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0xF2", 13 | "alpha" : "1.000", 14 | "blue" : "0xFA", 15 | "green" : "0xF6" 16 | } 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/launch-assets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/launch-assets/rw-logo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "Razewarelogo_1024.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/launch-assets/rw-logo.imageset/Razewarelogo_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/launch-assets/rw-logo.imageset/Razewarelogo_1024.png -------------------------------------------------------------------------------- /11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/launch-assets/rwdevcon-bg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "rwdevcon-bg.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/launch-assets/rwdevcon-bg.imageset/rwdevcon-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Assets.xcassets/launch-assets/rwdevcon-bg.imageset/rwdevcon-bg.png -------------------------------------------------------------------------------- /11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /11-building-for-testability/BookProjects/SwiftCalc/SwiftCalc/SwiftCalc.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /11-building-for-testability/BookProjects/SwiftCalc/SwiftCalcUITests/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 | 22 | 23 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Kuchi.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Kuchi.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Kuchi.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "CypherPoetCore", 6 | "repositoryURL": "https://github.com/CypherPoet/CypherPoetCore.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "f201d045ea3a6215d289cbf3cb5bbfd25ac73398", 10 | "version": "0.0.20" 11 | } 12 | }, 13 | { 14 | "package": "CypherPoetSwiftUIKit", 15 | "repositoryURL": "https://github.com/CypherPoet/CypherPoetSwiftUIKit.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "f72c2dd6075d8eb8b68a178a7cb61425f35cc7d2", 19 | "version": "0.0.32" 20 | } 21 | }, 22 | { 23 | "package": "CypherPoetUIKit", 24 | "repositoryURL": "https://github.com/CypherPoet/CypherPoetUIKit.git", 25 | "state": { 26 | "branch": null, 27 | "revision": "2e943efd0fd95884880203b8747ba0b98e66e8b7", 28 | "version": "0.0.9" 29 | } 30 | } 31 | ] 32 | }, 33 | "version": 1 34 | } 35 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Kuchi/Data/Models/FlashCard/FlashCard+Equatable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlashCard+Equatable.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 12/22/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | extension FlashCard: Equatable { 13 | 14 | static func == (lhs: FlashCard, rhs: FlashCard) -> Bool { 15 | lhs.wordCard.word.original == rhs.wordCard.word.original && 16 | lhs.wordCard.word.translation == rhs.wordCard.word.translation 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Kuchi/Data/Models/FlashCard/FlashCard.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlashCard.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 12/22/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | import Learning 11 | 12 | 13 | struct FlashCard { 14 | let id = UUID() 15 | 16 | var wordCard: WordCard 17 | var isActive: Bool = true 18 | } 19 | 20 | 21 | extension FlashCard: Identifiable {} 22 | 23 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Kuchi/Data/Models/FlashCard/FlashCardDeck.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlashCardDeck.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 12/22/19. 6 | // ✌️ 7 | // 8 | 9 | import Combine 10 | import Learning 11 | 12 | 13 | final class FlashCardDeck { 14 | @Published var cards: [FlashCard] 15 | 16 | 17 | init(cards: [FlashCard] = []) { 18 | self.cards = cards 19 | } 20 | } 21 | 22 | extension FlashCardDeck: ObservableObject {} 23 | 24 | 25 | // MARK: - Convenience Initializers 26 | extension FlashCardDeck { 27 | 28 | convenience init(from wordCards: [WordCard]) { 29 | let flashCards = wordCards.map { FlashCard(wordCard: $0) } 30 | 31 | self.init(cards: flashCards) 32 | } 33 | } 34 | 35 | 36 | // MARK: - Computeds 37 | extension FlashCardDeck { 38 | var isEmpty: Bool { cards.isEmpty } 39 | } 40 | 41 | 42 | // MARK: - Public Helpers 43 | extension FlashCardDeck { 44 | 45 | func getNextCard() -> FlashCard? { 46 | guard !isEmpty else { return nil } 47 | 48 | return cards.removeLast() 49 | } 50 | 51 | 52 | func getLastCard() -> FlashCard? { 53 | cards.last 54 | } 55 | } 56 | 57 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Kuchi/Data/Models/User.swift: -------------------------------------------------------------------------------- 1 | // 2 | // User.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/29/19. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | 13 | struct User { 14 | var isRegistered = false 15 | // var profile = UserProfile() 16 | 17 | // init() {} 18 | // 19 | // init(name: String) { 20 | // self.profile = UserProfile(name: name) 21 | // } 22 | } 23 | 24 | 25 | // MARK: - Computeds 26 | extension User { 27 | // var isRegistered: Bool { profile.name != nil } 28 | } 29 | 30 | 31 | 32 | 33 | extension User: Codable {} 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Kuchi/Data/Models/UserProfile.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserProfile.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/19/19. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | 13 | struct UserProfile { 14 | var name: String? 15 | } 16 | 17 | 18 | extension UserProfile: Codable {} 19 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Kuchi/Data/State/AppState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppState.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/29/19. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | import CypherPoetSwiftUIKit_DataFlowUtils 11 | 12 | 13 | struct AppState { 14 | var learningState = LearningState() 15 | var userState = UserState() 16 | var userProfileState = UserProfileState() 17 | var settingsState = SettingsState() 18 | } 19 | 20 | 21 | 22 | enum AppAction { 23 | case learning(_ action: LearningAction) 24 | case user(_ action: UserAction) 25 | case userProfile(_ action: UserProfileAction) 26 | } 27 | 28 | 29 | 30 | // MARK: - Reducer 31 | let appReducer: Reducer = Reducer(reduce: { appState, action in 32 | switch action { 33 | case let .user(action): 34 | userReducer.reduce(&appState.userState, action) 35 | case let .userProfile(action): 36 | userProfileReducer.reduce(&appState.userProfileState, action) 37 | case let .learning(action): 38 | learningReducer.reduce(&appState.learningState, action) 39 | } 40 | }) 41 | 42 | 43 | typealias AppStore = Store 44 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Kuchi/Data/State/LearningState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LearningState.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 12/22/19. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | import Combine 11 | import CypherPoetSwiftUIKit_DataFlowUtils 12 | 13 | 14 | struct LearningState { 15 | var flashCardDeck: FlashCardDeck 16 | 17 | 18 | init() { 19 | self.flashCardDeck = .init(from: DeckBuilder.learning.build()) 20 | } 21 | } 22 | 23 | 24 | 25 | //enum LearningSideEffect: SideEffect { 26 | // 27 | //} 28 | 29 | 30 | 31 | enum LearningAction { 32 | case flashCardDeckSet(FlashCardDeck) 33 | } 34 | 35 | 36 | // MARK: - Reducer 37 | let learningReducer: Reducer = Reducer( 38 | reduce: { state, action in 39 | switch action { 40 | case .flashCardDeckSet(let flashCardDeck): 41 | state.flashCardDeck = flashCardDeck 42 | } 43 | } 44 | ) 45 | 46 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Kuchi/Data/State/SettingsState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsState.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/29/19. 6 | // ✌️ 7 | // 8 | 9 | import Combine 10 | import CypherPoetCore_PropertyWrappers 11 | import CypherPoetSwiftUIKit_DataFlowUtils 12 | 13 | 14 | struct SettingsState { 15 | 16 | @UserDefault(UserDefaultsKey.shouldPersistProfile, defaultValue: true) 17 | var shouldPersistProfile: Bool 18 | 19 | } 20 | 21 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Kuchi/Data/State/UserState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserState.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/19/19. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | import Combine 11 | import SwiftUI 12 | import CypherPoetSwiftUIKit_DataFlowUtils 13 | 14 | 15 | 16 | struct UserState { 17 | @EnvironmentObject private var store: Store 18 | 19 | var user = User() 20 | } 21 | 22 | 23 | 24 | // MARK: - Computeds 25 | extension UserState { 26 | var isRegistered: Bool { user.isRegistered } 27 | var settingsState: SettingsState { store.state.settingsState } 28 | } 29 | 30 | 31 | 32 | // MARK: - Public Methods 33 | extension UserState { 34 | } 35 | 36 | 37 | // MARK: - Private Helpers 38 | private extension UserState { 39 | 40 | } 41 | 42 | 43 | 44 | enum UserAction { 45 | case save(_ user: User) 46 | case register 47 | } 48 | 49 | 50 | 51 | // MARK: - Reducer 52 | let userReducer: Reducer = Reducer(reduce: { userState, action in 53 | switch action { 54 | case let .save(user): 55 | userState.user = user 56 | case .register: 57 | userState.user.isRegistered = true 58 | } 59 | }) 60 | 61 | 62 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Kuchi/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Kuchi/Preview Content/Sample Data/SampleFlashCardDeck.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlashCardDeck.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 12/22/19. 6 | // ✌️ 7 | // 8 | 9 | #if DEBUG 10 | 11 | import Foundation 12 | import Learning 13 | import Languages 14 | 15 | 16 | enum SampleFlashCardDeck { 17 | static let `default` = FlashCardDeck(from: DeckBuilder.default.build()) 18 | } 19 | 20 | 21 | 22 | enum SampleFlashCard { 23 | static let `default` = FlashCard(wordCard: SampleWordCard.apple) 24 | } 25 | 26 | 27 | enum SampleWordCard { 28 | static let apple = WordCard( 29 | from: TranslatedWord( 30 | from: "Apple", 31 | withPronunciation: "Apple", 32 | andTranslation: "Omena" 33 | ) 34 | ) 35 | } 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Kuchi/Preview Content/Sample Data/Samples.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Samples.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/30/19. 6 | // ✌️ 7 | // 8 | 9 | #if DEBUG 10 | 11 | import Foundation 12 | import CypherPoetSwiftUIKit_DataFlowUtils 13 | 14 | 15 | enum SampleAppState { 16 | static let empty = AppState() 17 | 18 | static let withUserProfile = AppState( 19 | userProfileState: UserProfileState( 20 | profile: UserProfile(name: "CypherPoet") 21 | ) 22 | ) 23 | } 24 | 25 | 26 | enum SampleUserState { 27 | static let `default` = UserState() 28 | } 29 | 30 | 31 | enum SampleSettingsState { 32 | static let `default` = SettingsState() 33 | } 34 | 35 | 36 | enum SampleUser { 37 | static let `default` = User() 38 | } 39 | 40 | 41 | enum SampleStore { 42 | 43 | static let empty: Store = { 44 | Store(initialState: SampleAppState.empty, appReducer: appReducer) 45 | }() 46 | 47 | 48 | static let withUserProfile: Store = { 49 | Store(initialState: SampleAppState.withUserProfile, appReducer: appReducer) 50 | }() 51 | } 52 | 53 | 54 | 55 | #endif 56 | 57 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Kuchi/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Kuchi/Resources/Assets.xcassets/Images/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Kuchi/Resources/Assets.xcassets/Images/swift_world.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "swift_world.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Kuchi/Resources/Assets.xcassets/Images/swift_world.imageset/swift_world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/12-handling-user-input/Kuchi/Kuchi/Resources/Assets.xcassets/Images/swift_world.imageset/swift_world.png -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Kuchi/Reusables/Constants/UserDefaultsKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserDefaultsKey.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/29/19. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | enum UserDefaultsKey { 13 | static let shouldPersistProfile = "should_persist_profile" 14 | } 15 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Kuchi/Reusables/Extensions/Text+TextStyleWithViewModifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Text+TextStyleWithViewModifier.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/25/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | extension Text { 13 | 14 | /// Allows a `ViewModifier` instance to be passed to a `View`'s `textStyle` modifier 15 | /// Inspiration from https://mecid.github.io/2019/08/28/composable-styling-in-swiftui/ 16 | public func textStyle(style: Style) -> some View { 17 | ModifiedContent(content: self, modifier: style) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Kuchi/Reusables/Extensions/TextField+TextFieldStyleWithViewModifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextField+TextFieldStyleWithViewModifier.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/25/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | extension TextField { 13 | 14 | /// Allows a `ViewModifier` instance to be passed to a `View`'s `textFieldStyle` modifier 15 | /// Inspiration from https://mecid.github.io/2019/08/28/composable-styling-in-swiftui/ 16 | public func textFieldStyle(style: Style) -> some View { 17 | ModifiedContent(content: self, modifier: style) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Kuchi/Reusables/Gestures/CardDragDirection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CardDragDirection.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 12/22/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | enum CardDragDirection { 13 | case left 14 | case right 15 | } 16 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Kuchi/Reusables/View Components/WelcomeMessageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WelcomeMessageView.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/24/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct WelcomeMessageView: View { 13 | } 14 | 15 | 16 | // MARK: - Body 17 | extension WelcomeMessageView { 18 | 19 | var body: some View { 20 | VStack(spacing: 4) { 21 | Text("Welcome to") 22 | .scaledSystemFont(size: 56, weight: .bold) 23 | .multilineTextAlignment(.leading) 24 | 25 | HStack { 26 | LogoImage() 27 | 28 | Text("Kuchi") 29 | .scaledSystemFont(size: 65, weight: .bold) 30 | .multilineTextAlignment(.trailing) 31 | } 32 | } 33 | .lineLimit(2) 34 | .fixedSize(horizontal: false, vertical: true) 35 | } 36 | } 37 | 38 | 39 | // MARK: - Preview 40 | struct WelcomeMessageView_Previews: PreviewProvider { 41 | 42 | static var previews: some View { 43 | WelcomeMessageView() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Kuchi/Reusables/View Modifiers/Text Field Styles/CustomRoundedTextFieldStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomRoundedTextFieldStyle.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/25/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct CustomRoundedTextFieldStyle: ViewModifier { 13 | 14 | func body(content: Content) -> some View { 15 | content 16 | .padding(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16)) 17 | .background(Color(UIColor.systemBackground)) 18 | .overlay( 19 | RoundedRectangle(cornerRadius: 12) 20 | .stroke(lineWidth: 2) 21 | .foregroundColor(.accentColor) 22 | ) 23 | .shadow(color: Color.gray.opacity(0.4), radius: 3, x: 1, y: 2) 24 | } 25 | } 26 | 27 | 28 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Kuchi/Reusables/Views/Flash Card/FlashCardView+ViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlashCardView+ViewModel.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 12/22/19. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | extension FlashCardView { 13 | 14 | struct ViewModel { 15 | var flashCard: FlashCard 16 | } 17 | } 18 | 19 | 20 | extension FlashCardView.ViewModel { 21 | var startingText: String { flashCard.wordCard.word.original } 22 | var answerText: String { flashCard.wordCard.word.translation } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Kuchi/Reusables/Views/LogoImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LogoImage.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/24/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct LogoImage: View { 13 | } 14 | 15 | 16 | // MARK: - Body 17 | extension LogoImage { 18 | 19 | var body: some View { 20 | Image(systemName: "table") 21 | .resizable() 22 | .frame(width: 60, height: 60) 23 | .cornerRadius(60 / 2) 24 | .background(Color(white: 0.9)) 25 | .clipShape(Circle()) 26 | } 27 | } 28 | 29 | 30 | // MARK: - Preview 31 | struct LogoImage_Previews: PreviewProvider { 32 | 33 | static var previews: some View { 34 | LogoImage() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Kuchi/Reusables/Views/WelcomeBackgroundImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WelcomeBackgroundImage.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/24/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct WelcomeBackgroundImage: View { 13 | } 14 | 15 | 16 | // MARK: - Body 17 | extension WelcomeBackgroundImage { 18 | 19 | var body: some View { 20 | Image("swift_world") 21 | .resizable() 22 | .aspectRatio(1 / 1, contentMode: .fill) 23 | .edgesIgnoringSafeArea(.all) 24 | .saturation(0.5) 25 | .blur(radius: 5) 26 | .opacity(0.08) 27 | } 28 | } 29 | 30 | 31 | // MARK: - Preview 32 | struct WelcomeBackgroundImage_Previews: PreviewProvider { 33 | 34 | static var previews: some View { 35 | WelcomeBackgroundImage() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Kuchi/Scenes/EntryView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EntryView.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/16/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct EntryView: View { 13 | @EnvironmentObject private var store: AppStore 14 | } 15 | 16 | 17 | extension EntryView { 18 | var isRegistered: Bool { store.state.userState.isRegistered } 19 | } 20 | 21 | 22 | extension EntryView { 23 | var body: some View { 24 | Group { 25 | if isRegistered { 26 | WelcomeView() 27 | } else { 28 | RegistrationView() 29 | } 30 | } 31 | } 32 | } 33 | 34 | 35 | struct EntryView_Previews: PreviewProvider { 36 | static var previews: some View { 37 | EntryView() 38 | .environmentObject(SampleStore.empty) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Kuchi/Scenes/Home/ProgressView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProgressView.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/19/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct ProgressView: View { 13 | } 14 | 15 | 16 | // MARK: - Body 17 | extension ProgressView { 18 | 19 | var body: some View { 20 | Text(/*@START_MENU_TOKEN@*/"Hello World!"/*@END_MENU_TOKEN@*/) 21 | } 22 | } 23 | 24 | 25 | // MARK: - Preview 26 | struct ProgressView_Previews: PreviewProvider { 27 | static var previews: some View { 28 | ProgressView() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Kuchi/Scenes/Learn/LearningContainerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LearningContainerView.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 12/22/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct LearningContainerView: View { 13 | private let learningViewModel: LearningViewModel 14 | 15 | 16 | init(flashCardDeck: FlashCardDeck) { 17 | self.learningViewModel = LearningViewModel(flashCardDeck: flashCardDeck) 18 | } 19 | } 20 | 21 | 22 | // MARK: - Body 23 | extension LearningContainerView { 24 | 25 | var body: some View { 26 | LearningView(viewModel: self.learningViewModel) 27 | } 28 | } 29 | 30 | 31 | // MARK: - Computeds 32 | extension LearningContainerView { 33 | } 34 | 35 | 36 | // MARK: - View Variables 37 | extension LearningContainerView { 38 | 39 | 40 | } 41 | 42 | 43 | 44 | // MARK: - Preview 45 | struct LearnContainerView_Previews: PreviewProvider { 46 | 47 | static var previews: some View { 48 | LearningContainerView( 49 | flashCardDeck: SampleFlashCardDeck.default 50 | ) 51 | .environmentObject(SampleStore.withUserProfile) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Kuchi/Scenes/Practice/ChoicesView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChoicesView.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/21/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct ChoicesView: View { 13 | let choices: [String] 14 | let onSelect: ((String) -> Void)? 15 | } 16 | 17 | 18 | // MARK: - Body 19 | extension ChoicesView { 20 | 21 | var body: some View { 22 | VStack { 23 | ForEach(choices, id: \.self) { choice in 24 | VStack { 25 | Button(action: { 26 | self.onSelect?(choice) 27 | }, label: { 28 | Text(choice) 29 | }) 30 | .font(.title) 31 | .padding() 32 | 33 | Divider() 34 | } 35 | } 36 | } 37 | } 38 | } 39 | 40 | 41 | // MARK: - Preview 42 | struct ChoicesView_Previews: PreviewProvider { 43 | 44 | static var previews: some View { 45 | ChoicesView( 46 | choices: ["Alpha", "Bravo", "Charilie"], 47 | onSelect: { _ in } 48 | ) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Kuchi/Scenes/Practice/QuestionView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QuestionView.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/22/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct QuestionView: View { 13 | var question: String 14 | } 15 | 16 | 17 | // MARK: - Body 18 | extension QuestionView { 19 | 20 | var body: some View { 21 | HStack { 22 | Text(question) 23 | .scaledSystemFont(size: 64) 24 | .allowsTightening(true) 25 | .padding(.horizontal) 26 | .allowsTightening(true) 27 | .foregroundColor(.pink) 28 | .lineLimit(5) 29 | .multilineTextAlignment(.center) 30 | .animation(.spring()) 31 | } 32 | } 33 | } 34 | 35 | 36 | // MARK: - Preview 37 | struct QuestionView_Previews: PreviewProvider { 38 | 39 | static var previews: some View { 40 | QuestionView(question: "ごめんなさい") 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Kuchi/Scenes/Practice/ScoreView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScoreView.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/22/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct ScoreView: View { 13 | let score: Int 14 | let maxPossibleScore: Int 15 | } 16 | 17 | 18 | // MARK: - Body 19 | extension ScoreView { 20 | 21 | var body: some View { 22 | HStack { 23 | Text("\(score)/\(maxPossibleScore)") 24 | .font(.caption) 25 | .padding(4) 26 | 27 | Spacer() 28 | } 29 | } 30 | } 31 | 32 | 33 | // MARK: - Preview 34 | struct ScoreView_Previews: PreviewProvider { 35 | 36 | static var previews: some View { 37 | ScoreView(score: 90, maxPossibleScore: 100) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Kuchi/Scenes/Profile/ProfileView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProfileView.swift 3 | // Kuchi 4 | // 5 | // Created by CypherPoet on 10/19/19. 6 | // ✌️ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct ProfileView: View { 13 | } 14 | 15 | 16 | // MARK: - Body 17 | extension ProfileView { 18 | 19 | var body: some View { 20 | Text(/*@START_MENU_TOKEN@*/"Hello World!"/*@END_MENU_TOKEN@*/) 21 | } 22 | } 23 | 24 | 25 | // MARK: - Preview 26 | struct ProfileView_Previews: PreviewProvider { 27 | static var previews: some View { 28 | ProfileView() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Packages/LanguageLearning/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Packages/LanguageLearning/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Packages/LanguageLearning/README.md: -------------------------------------------------------------------------------- 1 | # LanguageLearning 2 | 3 | A description of this package. 4 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Packages/LanguageLearning/Sources/Learning/WordCard.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import Combine 3 | 4 | import Languages 5 | 6 | /// Provides a word challenge to complete with an origin word and translation to use 7 | /// for assessment (from `TranslatedWord`) and flags to use for measuring progress. 8 | public struct WordCard { 9 | 10 | /// `TranslatedWord` to use for assessment (includes original word and translation). 11 | public let word: TranslatedWord 12 | 13 | /// Determines whether this challenge has been completed. 14 | public var completed: Bool = false 15 | 16 | /// Determines whether this challenge was completed successfully. 17 | public var succeeded: Bool = false 18 | 19 | /// Initializes a new `WordCard` from a given `TranslatedWord`. 20 | public init(from word: TranslatedWord) { 21 | self.word = word 22 | } 23 | } 24 | 25 | 26 | extension WordCard: Hashable { 27 | public static func == (lhs: WordCard, rhs: WordCard) -> Bool { 28 | lhs.word.original == rhs.word.original 29 | } 30 | 31 | public func hash(into hasher: inout Hasher) { 32 | word.original.hash(into: &hasher) 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Packages/LanguageLearning/Tests/LearningTests/LearningTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Learning 3 | 4 | final class LearningTests: XCTestCase { 5 | } 6 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Packages/LanguageLearning/Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | 4 | var tests = [XCTestCaseEntry]() 5 | 6 | XCTMain(tests) 7 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Packages/Languages/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Packages/Languages/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "Languages", 8 | products: [ 9 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 10 | .library( 11 | name: "Languages", 12 | targets: ["Languages"]), 13 | ], 14 | dependencies: [ 15 | // Dependencies declare other packages that this package depends on. 16 | // .package(url: /* package url */, from: "1.0.0"), 17 | ], 18 | targets: [ 19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 20 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 21 | .target( 22 | name: "Languages", 23 | dependencies: []), 24 | .testTarget( 25 | name: "LanguagesTests", 26 | dependencies: ["Languages"]), 27 | ] 28 | ) 29 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Packages/Languages/README.md: -------------------------------------------------------------------------------- 1 | # Languages 2 | 3 | A description of this package. 4 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Packages/Languages/Tests/LanguagesTests/LanguagesTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Languages 3 | 4 | final class LanguagesTests: XCTestCase { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Packages/Languages/Tests/LanguagesTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(LanguagesTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Packages/Languages/Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import LanguagesTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += LanguagesTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Resources/Images/swift_world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/12-handling-user-input/Kuchi/Resources/Images/swift_world.png -------------------------------------------------------------------------------- /12-handling-user-input/Kuchi/Resources/jp.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "original": "はい", "pronunciation": "hai", "translation": "yes" }, 3 | { "original": "いいえ", "pronunciation": "iie", "translation": "no" }, 4 | { "original": "おねがい します", "pronunciation": "onegai shimasu", "translation": "please" }, 5 | { "original": "こんにちわ", "pronunciation": "konnichiwa", "translation": "hello" }, 6 | { "original": "はじめまして", "pronunciation": "hajimemashite", "translation": "nice to meet you" }, 7 | { "original": "もしもし", "pronunciation": "moshi moshi", "translation": "hello" }, 8 | { "original": "すみません", "pronunciation": "sumimasen", "translation": "excuse me" }, 9 | { "original": "ありがとう", "pronunciation": "arigatō", "translation": "thank you" }, 10 | { "original": "ごめんなさい", "pronunciation": "gomenasai", "translation": "sorry" } 11 | ] 12 | -------------------------------------------------------------------------------- /13-drawing-and-custom-graphics/MountainAirport/MountainAirport.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /13-drawing-and-custom-graphics/MountainAirport/MountainAirport.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /13-drawing-and-custom-graphics/MountainAirport/MountainAirport.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "CypherPoetSwiftUIKit", 6 | "repositoryURL": "https://github.com/CypherPoet/CypherPoetSwiftUIKit.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "c8a3ad97acb42ff91708a014ddec73d63bacb55b", 10 | "version": "0.0.36" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /13-drawing-and-custom-graphics/MountainAirport/MountainAirport/Data/Constants/FlightDirection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlightDirection.swift 3 | // MountainAirport 4 | // 5 | // Created by CypherPoet on 11/1/19. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | public enum FlightDirection { 13 | case arrival 14 | case departure 15 | } 16 | 17 | 18 | extension FlightDirection { 19 | 20 | var boardName: String { 21 | switch self { 22 | case .arrival: 23 | return "Arrivals" 24 | case .departure: 25 | return "Departures" 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /13-drawing-and-custom-graphics/MountainAirport/MountainAirport/Data/Constants/FlightStatus.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlightStatus.swift 3 | // MountainAirport 4 | // 5 | // Created by CypherPoet on 11/1/19. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | public enum FlightStatus: String, CaseIterable { 13 | case onTime 14 | case delayed 15 | case cancelled 16 | case landed 17 | case departed 18 | } 19 | 20 | 21 | extension FlightStatus { 22 | 23 | var displayName: String { 24 | switch self { 25 | case .onTime: 26 | return "On Time" 27 | case .delayed: 28 | return "Delayed" 29 | case .cancelled: 30 | return "Cancelled" 31 | case .landed: 32 | return "Landed" 33 | case .departed: 34 | return "Departed" 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /13-drawing-and-custom-graphics/MountainAirport/MountainAirport/Data/Models/Awards/Award+BadgeView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Award+BadgeView.swift 3 | // MountainAirport 4 | // 5 | // Created by CypherPoet on 2/5/20. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | 13 | extension Award { 14 | var badgeView: AnyView { 15 | switch self { 16 | case .firstVisit: 17 | return AnyView(FirstVisitAwardView()) 18 | case .rideShareToAirport: 19 | return AnyView(RideShareToAirportAwardView()) 20 | case .airportLoungeVisit: 21 | return AnyView(AirportLoungeAwardView()) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /13-drawing-and-custom-graphics/MountainAirport/MountainAirport/Data/Models/Awards/Award+Computeds.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Award+Computeds.swift 3 | // MountainAirport 4 | // 5 | // Created by CypherPoet on 2/5/20. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | extension Award { 13 | 14 | var title: String { 15 | switch self { 16 | case .firstVisit: 17 | return "First Airport Visit" 18 | case .rideShareToAirport: 19 | return "Ride Share to Airport" 20 | case .airportLoungeVisit: 21 | return "Lounge Visit" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /13-drawing-and-custom-graphics/MountainAirport/MountainAirport/Data/Models/Awards/Award.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Award.swift 3 | // MountainAirport 4 | // 5 | // Created by CypherPoet on 2/5/20. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | 13 | enum Award: String { 14 | case firstVisit 15 | case rideShareToAirport 16 | case airportLoungeVisit 17 | } 18 | 19 | 20 | extension Award: CaseIterable {} 21 | 22 | extension Award: Identifiable { 23 | var id: String { self.rawValue } 24 | } 25 | -------------------------------------------------------------------------------- /13-drawing-and-custom-graphics/MountainAirport/MountainAirport/Data/State/AppState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppState.swift 3 | // MountainAirport 4 | // 5 | // Created by CypherPoet on 11/1/19. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | import CypherPoetSwiftUIKit_DataFlowUtils 11 | 12 | 13 | struct AppState { 14 | var awardsState = AwardsState() 15 | var flightInformationState = FlightInformationState() 16 | } 17 | 18 | 19 | enum AppAction { 20 | case awards(_ action: AwardsAction) 21 | case flightInfo(_ action: FlightInformationAction) 22 | } 23 | 24 | 25 | // MARK: - Reducer 26 | let appReducer = Reducer { appState, action in 27 | switch action { 28 | case let .awards(action): 29 | awardsReducer.reduce(&appState.awardsState, action) 30 | case let .flightInfo(action): 31 | flightInformationReducer.reduce(&appState.flightInformationState, action) 32 | } 33 | } 34 | 35 | typealias AppStore = Store 36 | -------------------------------------------------------------------------------- /13-drawing-and-custom-graphics/MountainAirport/MountainAirport/Data/State/AwardsState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AwardsState.swift 3 | // MountainAirport 4 | // 5 | // Created by CypherPoet on 2/5/20. 6 | // ✌️ 7 | // 8 | 9 | 10 | import Foundation 11 | import Combine 12 | import CypherPoetSwiftUIKit_DataFlowUtils 13 | 14 | 15 | struct AwardsState { 16 | var earnedAwards: [Award] = [] 17 | } 18 | 19 | 20 | 21 | //enum AwardsSideEffect: SideEffect { 22 | // 23 | //} 24 | 25 | 26 | 27 | enum AwardsAction { 28 | case earnedAwardsSet([Award]) 29 | } 30 | 31 | 32 | // MARK: - Reducer 33 | let awardsReducer: Reducer = Reducer( 34 | reduce: { state, action in 35 | switch action { 36 | case .earnedAwardsSet(let awards): 37 | state.earnedAwards = awards 38 | } 39 | } 40 | ) 41 | 42 | -------------------------------------------------------------------------------- /13-drawing-and-custom-graphics/MountainAirport/MountainAirport/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /13-drawing-and-custom-graphics/MountainAirport/MountainAirport/Preview Content/Sample Data/SampleData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SampleData.swift 3 | // MountainAirport 4 | // 5 | // Created by CypherPoet on 11/1/19. 6 | // ✌️ 7 | // 8 | 9 | #if DEBUG 10 | 11 | import Foundation 12 | 13 | enum SampleFlights { 14 | static let `default` = FlightInformation.generateFlights() 15 | } 16 | 17 | 18 | enum SampleAwardsState { 19 | static let `default` = AwardsState( 20 | earnedAwards: [ 21 | .firstVisit, 22 | .rideShareToAirport, 23 | .airportLoungeVisit, 24 | ] 25 | ) 26 | } 27 | 28 | 29 | enum SampleFlightInformationState { 30 | static let `default` = FlightInformationState(flightInfo: SampleFlights.default) 31 | } 32 | 33 | 34 | 35 | 36 | enum SampleAppState { 37 | static let `default` = AppState( 38 | awardsState: SampleAwardsState.default, 39 | flightInformationState: SampleFlightInformationState.default 40 | ) 41 | } 42 | 43 | 44 | enum SampleStore { 45 | static let `default` = AppStore(initialState: SampleAppState.default, appReducer: appReducer) 46 | } 47 | 48 | 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /13-drawing-and-custom-graphics/MountainAirport/MountainAirport/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /13-drawing-and-custom-graphics/MountainAirport/MountainAirport/Resources/Assets.xcassets/terminal-a-map.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "terminal-a-map.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /13-drawing-and-custom-graphics/MountainAirport/MountainAirport/Resources/Assets.xcassets/terminal-a-map.imageset/terminal-a-map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/13-drawing-and-custom-graphics/MountainAirport/MountainAirport/Resources/Assets.xcassets/terminal-a-map.imageset/terminal-a-map.png -------------------------------------------------------------------------------- /13-drawing-and-custom-graphics/MountainAirport/MountainAirport/Resources/Assets.xcassets/terminal-b-map.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "terminal-b-map.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /13-drawing-and-custom-graphics/MountainAirport/MountainAirport/Resources/Assets.xcassets/terminal-b-map.imageset/terminal-b-map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/13-drawing-and-custom-graphics/MountainAirport/MountainAirport/Resources/Assets.xcassets/terminal-b-map.imageset/terminal-b-map.png -------------------------------------------------------------------------------- /14-animations/MountainAirport/MountainAirport.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /14-animations/MountainAirport/MountainAirport.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /14-animations/MountainAirport/MountainAirport.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "CypherPoetSwiftUIKit", 6 | "repositoryURL": "https://github.com/CypherPoet/CypherPoetSwiftUIKit.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "c8a3ad97acb42ff91708a014ddec73d63bacb55b", 10 | "version": "0.0.36" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /14-animations/MountainAirport/MountainAirport/Data/Constants/FlightDirection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlightDirection.swift 3 | // MountainAirport 4 | // 5 | // Created by CypherPoet on 11/1/19. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | public enum FlightDirection { 13 | case arrival 14 | case departure 15 | } 16 | 17 | 18 | extension FlightDirection { 19 | 20 | var boardName: String { 21 | switch self { 22 | case .arrival: 23 | return "Arrivals" 24 | case .departure: 25 | return "Departures" 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /14-animations/MountainAirport/MountainAirport/Data/Constants/FlightStatus.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlightStatus.swift 3 | // MountainAirport 4 | // 5 | // Created by CypherPoet on 11/1/19. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | public enum FlightStatus: String, CaseIterable { 13 | case onTime 14 | case delayed 15 | case cancelled 16 | case landed 17 | case departed 18 | } 19 | 20 | 21 | extension FlightStatus { 22 | 23 | var displayName: String { 24 | switch self { 25 | case .onTime: 26 | return "On Time" 27 | case .delayed: 28 | return "Delayed" 29 | case .cancelled: 30 | return "Cancelled" 31 | case .landed: 32 | return "Landed" 33 | case .departed: 34 | return "Departed" 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /14-animations/MountainAirport/MountainAirport/Data/Models/Awards/Award+BadgeView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Award+BadgeView.swift 3 | // MountainAirport 4 | // 5 | // Created by CypherPoet on 2/5/20. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | 13 | extension Award { 14 | var badgeView: AnyView { 15 | switch self { 16 | case .firstVisit: 17 | return AnyView(FirstVisitAwardView()) 18 | case .rideShareToAirport: 19 | return AnyView(RideShareToAirportAwardView()) 20 | case .airportLoungeVisit: 21 | return AnyView(AirportLoungeAwardView()) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /14-animations/MountainAirport/MountainAirport/Data/Models/Awards/Award+Computeds.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Award+Computeds.swift 3 | // MountainAirport 4 | // 5 | // Created by CypherPoet on 2/5/20. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | extension Award { 13 | 14 | var title: String { 15 | switch self { 16 | case .firstVisit: 17 | return "First Airport Visit" 18 | case .rideShareToAirport: 19 | return "Ride Share to Airport" 20 | case .airportLoungeVisit: 21 | return "Lounge Visit" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /14-animations/MountainAirport/MountainAirport/Data/Models/Awards/Award.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Award.swift 3 | // MountainAirport 4 | // 5 | // Created by CypherPoet on 2/5/20. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | 13 | enum Award: String { 14 | case firstVisit 15 | case rideShareToAirport 16 | case airportLoungeVisit 17 | } 18 | 19 | 20 | extension Award: CaseIterable {} 21 | 22 | extension Award: Identifiable { 23 | var id: String { self.rawValue } 24 | } 25 | -------------------------------------------------------------------------------- /14-animations/MountainAirport/MountainAirport/Data/State/AppState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppState.swift 3 | // MountainAirport 4 | // 5 | // Created by CypherPoet on 11/1/19. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | import CypherPoetSwiftUIKit_DataFlowUtils 11 | 12 | 13 | struct AppState { 14 | var awardsState = AwardsState() 15 | var flightInformationState = FlightInformationState() 16 | } 17 | 18 | 19 | enum AppAction { 20 | case awards(_ action: AwardsAction) 21 | case flightInfo(_ action: FlightInformationAction) 22 | } 23 | 24 | 25 | // MARK: - Reducer 26 | let appReducer = Reducer { appState, action in 27 | switch action { 28 | case let .awards(action): 29 | awardsReducer.reduce(&appState.awardsState, action) 30 | case let .flightInfo(action): 31 | flightInformationReducer.reduce(&appState.flightInformationState, action) 32 | } 33 | } 34 | 35 | typealias AppStore = Store 36 | -------------------------------------------------------------------------------- /14-animations/MountainAirport/MountainAirport/Data/State/AwardsState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AwardsState.swift 3 | // MountainAirport 4 | // 5 | // Created by CypherPoet on 2/5/20. 6 | // ✌️ 7 | // 8 | 9 | 10 | import Foundation 11 | import Combine 12 | import CypherPoetSwiftUIKit_DataFlowUtils 13 | 14 | 15 | struct AwardsState { 16 | var earnedAwards: [Award] = [] 17 | } 18 | 19 | 20 | 21 | //enum AwardsSideEffect: SideEffect { 22 | // 23 | //} 24 | 25 | 26 | 27 | enum AwardsAction { 28 | case earnedAwardsSet([Award]) 29 | } 30 | 31 | 32 | // MARK: - Reducer 33 | let awardsReducer: Reducer = Reducer( 34 | reduce: { state, action in 35 | switch action { 36 | case .earnedAwardsSet(let awards): 37 | state.earnedAwards = awards 38 | } 39 | } 40 | ) 41 | 42 | -------------------------------------------------------------------------------- /14-animations/MountainAirport/MountainAirport/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /14-animations/MountainAirport/MountainAirport/Preview Content/Sample Data/SampleData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SampleData.swift 3 | // MountainAirport 4 | // 5 | // Created by CypherPoet on 11/1/19. 6 | // ✌️ 7 | // 8 | 9 | #if DEBUG 10 | 11 | import Foundation 12 | 13 | enum SampleFlights { 14 | static let `default` = FlightInformation.generateFlights() 15 | } 16 | 17 | 18 | enum SampleAwardsState { 19 | static let `default` = AwardsState( 20 | earnedAwards: [ 21 | .firstVisit, 22 | .rideShareToAirport, 23 | .airportLoungeVisit, 24 | ] 25 | ) 26 | } 27 | 28 | 29 | enum SampleFlightInformationState { 30 | static let `default` = FlightInformationState(flightInfo: SampleFlights.default) 31 | } 32 | 33 | 34 | 35 | 36 | enum SampleAppState { 37 | static let `default` = AppState( 38 | awardsState: SampleAwardsState.default, 39 | flightInformationState: SampleFlightInformationState.default 40 | ) 41 | } 42 | 43 | 44 | enum SampleStore { 45 | static let `default` = AppStore(initialState: SampleAppState.default, appReducer: appReducer) 46 | } 47 | 48 | 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /14-animations/MountainAirport/MountainAirport/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /14-animations/MountainAirport/MountainAirport/Resources/Assets.xcassets/terminal-a-map.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "terminal-a-map.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /14-animations/MountainAirport/MountainAirport/Resources/Assets.xcassets/terminal-a-map.imageset/terminal-a-map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/14-animations/MountainAirport/MountainAirport/Resources/Assets.xcassets/terminal-a-map.imageset/terminal-a-map.png -------------------------------------------------------------------------------- /14-animations/MountainAirport/MountainAirport/Resources/Assets.xcassets/terminal-b-map.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "terminal-b-map.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /14-animations/MountainAirport/MountainAirport/Resources/Assets.xcassets/terminal-b-map.imageset/terminal-b-map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/14-animations/MountainAirport/MountainAirport/Resources/Assets.xcassets/terminal-b-map.imageset/terminal-b-map.png -------------------------------------------------------------------------------- /15-complex-interfaces/MountainAirport/MountainAirport.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /15-complex-interfaces/MountainAirport/MountainAirport.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /15-complex-interfaces/MountainAirport/MountainAirport.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "Burritos", 6 | "repositoryURL": "https://github.com/guillermomuntaner/Burritos.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "309dbe1b5b3af8839ca6a7ebb2ad6ddf041bf420", 10 | "version": "0.0.3" 11 | } 12 | }, 13 | { 14 | "package": "CypherPoetSwiftUIKit", 15 | "repositoryURL": "https://github.com/CypherPoet/CypherPoetSwiftUIKit.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "c8a3ad97acb42ff91708a014ddec73d63bacb55b", 19 | "version": "0.0.36" 20 | } 21 | } 22 | ] 23 | }, 24 | "version": 1 25 | } 26 | -------------------------------------------------------------------------------- /15-complex-interfaces/MountainAirport/MountainAirport/Data/Constants/FlightDirection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlightDirection.swift 3 | // MountainAirport 4 | // 5 | // Created by CypherPoet on 11/1/19. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | public enum FlightDirection { 13 | case arrival 14 | case departure 15 | } 16 | 17 | 18 | extension FlightDirection { 19 | 20 | var boardName: String { 21 | switch self { 22 | case .arrival: 23 | return "Arrivals" 24 | case .departure: 25 | return "Departures" 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /15-complex-interfaces/MountainAirport/MountainAirport/Data/Constants/FlightStatus.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlightStatus.swift 3 | // MountainAirport 4 | // 5 | // Created by CypherPoet on 11/1/19. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | public enum FlightStatus: String, CaseIterable { 13 | case onTime 14 | case delayed 15 | case cancelled 16 | case landed 17 | case departed 18 | } 19 | 20 | 21 | extension FlightStatus { 22 | 23 | var displayName: String { 24 | switch self { 25 | case .onTime: 26 | return "On Time" 27 | case .delayed: 28 | return "Delayed" 29 | case .cancelled: 30 | return "Cancelled" 31 | case .landed: 32 | return "Landed" 33 | case .departed: 34 | return "Departed" 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /15-complex-interfaces/MountainAirport/MountainAirport/Data/Models/Awards/Award+BadgeView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Award+BadgeView.swift 3 | // MountainAirport 4 | // 5 | // Created by CypherPoet on 2/5/20. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | 13 | extension Award { 14 | var badgeView: AnyView { 15 | switch self { 16 | case .firstVisit: 17 | return AnyView(FirstVisitAwardView()) 18 | case .rideShareToAirport: 19 | return AnyView(RideShareToAirportAwardView()) 20 | case .airportLoungeVisit: 21 | return AnyView(AirportLoungeAwardView()) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /15-complex-interfaces/MountainAirport/MountainAirport/Data/Models/Awards/Award+Computeds.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Award+Computeds.swift 3 | // MountainAirport 4 | // 5 | // Created by CypherPoet on 2/5/20. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | extension Award { 13 | 14 | var title: String { 15 | switch self { 16 | case .firstVisit: 17 | return "First Airport Visit" 18 | case .rideShareToAirport: 19 | return "Ride Share to Airport" 20 | case .airportLoungeVisit: 21 | return "Lounge Visit" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /15-complex-interfaces/MountainAirport/MountainAirport/Data/Models/Awards/Award.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Award.swift 3 | // MountainAirport 4 | // 5 | // Created by CypherPoet on 2/5/20. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | 13 | enum Award: String { 14 | case firstVisit 15 | case rideShareToAirport 16 | case airportLoungeVisit 17 | } 18 | 19 | 20 | extension Award: CaseIterable {} 21 | 22 | extension Award: Identifiable { 23 | var id: String { self.rawValue } 24 | } 25 | -------------------------------------------------------------------------------- /15-complex-interfaces/MountainAirport/MountainAirport/Data/State/AppState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppState.swift 3 | // MountainAirport 4 | // 5 | // Created by CypherPoet on 11/1/19. 6 | // ✌️ 7 | // 8 | 9 | import Foundation 10 | import CypherPoetSwiftUIKit_DataFlowUtils 11 | 12 | 13 | struct AppState { 14 | var awardsState = AwardsState() 15 | var flightInformationState = FlightInformationState() 16 | } 17 | 18 | 19 | enum AppAction { 20 | case awards(_ action: AwardsAction) 21 | case flightInfo(_ action: FlightInformationAction) 22 | } 23 | 24 | 25 | // MARK: - Reducer 26 | let appReducer = Reducer { appState, action in 27 | switch action { 28 | case let .awards(action): 29 | awardsReducer.reduce(&appState.awardsState, action) 30 | case let .flightInfo(action): 31 | flightInformationReducer.reduce(&appState.flightInformationState, action) 32 | } 33 | } 34 | 35 | typealias AppStore = Store 36 | -------------------------------------------------------------------------------- /15-complex-interfaces/MountainAirport/MountainAirport/Data/State/AwardsState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AwardsState.swift 3 | // MountainAirport 4 | // 5 | // Created by CypherPoet on 2/5/20. 6 | // ✌️ 7 | // 8 | 9 | 10 | import Foundation 11 | import Combine 12 | import CypherPoetSwiftUIKit_DataFlowUtils 13 | 14 | 15 | struct AwardsState { 16 | var earnedAwards: [Award] = [] 17 | } 18 | 19 | 20 | 21 | //enum AwardsSideEffect: SideEffect { 22 | // 23 | //} 24 | 25 | 26 | 27 | enum AwardsAction { 28 | case earnedAwardsSet([Award]) 29 | } 30 | 31 | 32 | // MARK: - Reducer 33 | let awardsReducer: Reducer = Reducer( 34 | reduce: { state, action in 35 | switch action { 36 | case .earnedAwardsSet(let awards): 37 | state.earnedAwards = awards 38 | } 39 | } 40 | ) 41 | 42 | -------------------------------------------------------------------------------- /15-complex-interfaces/MountainAirport/MountainAirport/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /15-complex-interfaces/MountainAirport/MountainAirport/Preview Content/Sample Data/SampleData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SampleData.swift 3 | // MountainAirport 4 | // 5 | // Created by CypherPoet on 11/1/19. 6 | // ✌️ 7 | // 8 | 9 | #if DEBUG 10 | 11 | import Foundation 12 | 13 | enum SampleFlights { 14 | static let `default` = FlightInformation.generateFlights() 15 | } 16 | 17 | 18 | enum SampleAwardsState { 19 | static let `default` = AwardsState( 20 | earnedAwards: [ 21 | .firstVisit, 22 | .rideShareToAirport, 23 | .airportLoungeVisit, 24 | ] 25 | ) 26 | } 27 | 28 | 29 | enum SampleFlightInformationState { 30 | static let `default` = FlightInformationState(flightInfo: SampleFlights.default) 31 | } 32 | 33 | 34 | 35 | 36 | enum SampleAppState { 37 | static let `default` = AppState( 38 | awardsState: SampleAwardsState.default, 39 | flightInformationState: SampleFlightInformationState.default 40 | ) 41 | } 42 | 43 | 44 | enum SampleStore { 45 | static let `default` = AppStore(initialState: SampleAppState.default, appReducer: appReducer) 46 | } 47 | 48 | 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /15-complex-interfaces/MountainAirport/MountainAirport/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /15-complex-interfaces/MountainAirport/MountainAirport/Resources/Assets.xcassets/terminal-a-map.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "terminal-a-map.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /15-complex-interfaces/MountainAirport/MountainAirport/Resources/Assets.xcassets/terminal-a-map.imageset/terminal-a-map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/15-complex-interfaces/MountainAirport/MountainAirport/Resources/Assets.xcassets/terminal-a-map.imageset/terminal-a-map.png -------------------------------------------------------------------------------- /15-complex-interfaces/MountainAirport/MountainAirport/Resources/Assets.xcassets/terminal-b-map.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "terminal-b-map.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /15-complex-interfaces/MountainAirport/MountainAirport/Resources/Assets.xcassets/terminal-b-map.imageset/terminal-b-map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/15-complex-interfaces/MountainAirport/MountainAirport/Resources/Assets.xcassets/terminal-b-map.imageset/terminal-b-map.png -------------------------------------------------------------------------------- /15-complex-interfaces/MountainAirport/MountainAirport/Scenes/Awards/Awards Grid/AwardsGridView+ViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AwardsGridView+ViewModel.swift 3 | // MountainAirport 4 | // 5 | // Created by CypherPoet on 2/5/20. 6 | // ✌️ 7 | // 8 | 9 | 10 | import SwiftUI 11 | import Combine 12 | 13 | 14 | extension AwardsGridView { 15 | struct ViewModel { 16 | let awardsState: AwardsState 17 | } 18 | } 19 | 20 | 21 | // MARK: - Publishers 22 | extension AwardsGridView.ViewModel { 23 | } 24 | 25 | 26 | // MARK: - Computeds 27 | extension AwardsGridView.ViewModel { 28 | var awards: [Award] { awardsState.earnedAwards } 29 | } 30 | 31 | 32 | // MARK: - Public Methods 33 | extension AwardsGridView.ViewModel { 34 | } 35 | 36 | 37 | 38 | // MARK: - Private Helpers 39 | private extension AwardsGridView.ViewModel { 40 | } 41 | -------------------------------------------------------------------------------- /15-complex-interfaces/MountainAirport/MountainAirport/Scenes/Awards/Awards List/AwardsListView+ViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AwardsListView+ViewModel.swift 3 | // MountainAirport 4 | // 5 | // Created by CypherPoet on 2/5/20. 6 | // ✌️ 7 | // 8 | 9 | 10 | import SwiftUI 11 | import Combine 12 | 13 | 14 | extension AwardsListView { 15 | struct ViewModel { 16 | let awardsState: AwardsState 17 | } 18 | } 19 | 20 | 21 | // MARK: - Publishers 22 | extension AwardsListView.ViewModel { 23 | } 24 | 25 | 26 | // MARK: - Computeds 27 | extension AwardsListView.ViewModel { 28 | var awards: [Award] { awardsState.earnedAwards } 29 | } 30 | 31 | 32 | // MARK: - Public Methods 33 | extension AwardsListView.ViewModel { 34 | } 35 | 36 | 37 | 38 | // MARK: - Private Helpers 39 | private extension AwardsListView.ViewModel { 40 | } 41 | -------------------------------------------------------------------------------- /15-complex-interfaces/MountainAirport/Screenshots/swiftui-grid-view.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CypherPoet/book--swiftui-by-tutorials/d210ebccc0059eb12e9aee088c85fe385857a3b3/15-complex-interfaces/MountainAirport/Screenshots/swiftui-grid-view.mp4 -------------------------------------------------------------------------------- /SwiftUIByTutorials.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | --------------------------------------------------------------------------------