├── 20-building-a-complete-app
├── NumberFacts
│ ├── Common
│ │ ├── README.md
│ │ ├── .gitignore
│ │ ├── Sources
│ │ │ ├── SupportedLanguages.swift
│ │ │ └── Models
│ │ │ │ ├── NumberFact
│ │ │ │ ├── NumberFact+Decoder.swift
│ │ │ │ ├── NumberFact+CoreDataClass.swift
│ │ │ │ ├── NumberFact+Category.swift
│ │ │ │ └── NumberFact+CoreDataProperties.swift
│ │ │ │ └── Language
│ │ │ │ └── Language.swift
│ │ ├── .swiftpm
│ │ │ └── xcode
│ │ │ │ └── package.xcworkspace
│ │ │ │ └── contents.xcworkspacedata
│ │ └── Package.swift
│ ├── NumbersAPIService
│ │ ├── README.md
│ │ ├── .gitignore
│ │ ├── Tests
│ │ │ ├── LinuxMain.swift
│ │ │ └── NumbersAPIService
│ │ │ │ └── XCTestManifests.swift
│ │ ├── Package.resolved
│ │ ├── Sources
│ │ │ ├── NumbersAPIService.swift
│ │ │ ├── NumbersAPIServiceError.swift
│ │ │ └── Extensions
│ │ │ │ └── Endpoint+NumbersAPI.swift
│ │ └── Package.swift
│ ├── TranslationService
│ │ ├── README.md
│ │ ├── .gitignore
│ │ ├── Sources
│ │ │ ├── TranslationAPIService.swift
│ │ │ ├── TranslationAPIServiceError.swift
│ │ │ └── Extensions
│ │ │ │ └── Endpoint+TranslationAPI.swift
│ │ └── Package.swift
│ ├── NumberFacts
│ │ ├── NumberFacts
│ │ │ ├── Resources
│ │ │ │ └── Assets.xcassets
│ │ │ │ │ └── Contents.json
│ │ │ ├── Preview Content
│ │ │ │ ├── Preview Assets.xcassets
│ │ │ │ │ └── Contents.json
│ │ │ │ └── Preivew Data
│ │ │ │ │ ├── PreviewData.swift
│ │ │ │ │ ├── PreviewData+AppStores.swift
│ │ │ │ │ └── PreviewData+NumberFacts.swift
│ │ │ ├── Data
│ │ │ │ ├── NumberFacts.xcdatamodeld
│ │ │ │ │ ├── .xccurrentversion
│ │ │ │ │ └── NumberFacts.xcdatamodel
│ │ │ │ │ │ └── contents
│ │ │ │ ├── CoreDataManager+Utils.swift
│ │ │ │ └── State
│ │ │ │ │ └── AppState.swift
│ │ │ ├── App
│ │ │ │ ├── CurrentApplication.swift
│ │ │ │ └── AppDelegate.swift
│ │ │ ├── Reusables
│ │ │ │ └── Extensions
│ │ │ │ │ ├── NumberFact+errorFact.swift
│ │ │ │ │ └── Collection+deleteManagedObjects.swift
│ │ │ ├── Scenes
│ │ │ │ ├── RootView.swift
│ │ │ │ ├── HomeView.swift
│ │ │ │ └── Favorite Number Facts
│ │ │ │ │ └── FavoriteNumberFactsListView.swift
│ │ │ └── Base.lproj
│ │ │ │ └── LaunchScreen.storyboard
│ │ ├── NumberFacts.xcodeproj
│ │ │ └── project.xcworkspace
│ │ │ │ └── contents.xcworkspacedata
│ │ └── NumberFactsTests
│ │ │ ├── Info.plist
│ │ │ └── NumberFactsTests.swift
│ ├── Design
│ │ └── screenshot-1.png
│ ├── Playgrounds
│ │ └── NumbersAPI.playground
│ │ │ ├── contents.xcplayground
│ │ │ └── Contents.swift
│ └── NumberFacts.xcworkspace
│ │ ├── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── swiftpm
│ │ │ └── Package.resolved
│ │ └── contents.xcworkspacedata
└── BookExample
│ ├── final
│ ├── ChuckNorrisJokes
│ │ ├── Assets.xcassets
│ │ │ ├── Contents.json
│ │ │ ├── Colors
│ │ │ │ ├── Contents.json
│ │ │ │ ├── Red.colorset
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── Green.colorset
│ │ │ │ │ └── Contents.json
│ │ │ │ └── Gray.colorset
│ │ │ │ │ └── Contents.json
│ │ │ └── AppIcon.appiconset
│ │ │ │ ├── Icon-App-20x20@1x.png
│ │ │ │ ├── Icon-App-20x20@2x.png
│ │ │ │ ├── Icon-App-20x20@3x.png
│ │ │ │ ├── Icon-App-29x29@1x.png
│ │ │ │ ├── Icon-App-29x29@2x.png
│ │ │ │ ├── Icon-App-29x29@3x.png
│ │ │ │ ├── Icon-App-40x40@1x.png
│ │ │ │ ├── Icon-App-40x40@2x.png
│ │ │ │ ├── Icon-App-40x40@3x.png
│ │ │ │ ├── Icon-App-60x60@2x.png
│ │ │ │ ├── Icon-App-60x60@3x.png
│ │ │ │ ├── Icon-App-76x76@1x.png
│ │ │ │ ├── Icon-App-76x76@2x.png
│ │ │ │ ├── ItunesArtwork@2x.png
│ │ │ │ └── Icon-App-83.5x83.5@2x.png
│ │ ├── Preview Content
│ │ │ └── Preview Assets.xcassets
│ │ │ │ └── Contents.json
│ │ ├── SampleJoke.json
│ │ ├── Models
│ │ │ └── ChuckNorrisJokes.xcdatamodeld
│ │ │ │ └── ChuckNorrisJokes.xcdatamodel
│ │ │ │ └── contents
│ │ └── Base.lproj
│ │ │ └── LaunchScreen.storyboard
│ ├── ChuckNorrisJokesTests
│ │ ├── TestTranslationResponse.json
│ │ ├── TestJoke.json
│ │ ├── TestTranslatedJoke.json
│ │ └── Info.plist
│ ├── ChuckNorrisJokes.xcodeproj
│ │ └── project.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── ChuckNorrisJokesModel
│ │ └── Info.plist
│ ├── starter
│ ├── ChuckNorrisJokes
│ │ ├── Assets.xcassets
│ │ │ ├── Contents.json
│ │ │ ├── Colors
│ │ │ │ ├── Contents.json
│ │ │ │ ├── Red.colorset
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── Green.colorset
│ │ │ │ │ └── Contents.json
│ │ │ │ └── Gray.colorset
│ │ │ │ │ └── Contents.json
│ │ │ └── AppIcon.appiconset
│ │ │ │ ├── Icon-App-20x20@1x.png
│ │ │ │ ├── Icon-App-20x20@2x.png
│ │ │ │ ├── Icon-App-20x20@3x.png
│ │ │ │ ├── Icon-App-29x29@1x.png
│ │ │ │ ├── Icon-App-29x29@2x.png
│ │ │ │ ├── Icon-App-29x29@3x.png
│ │ │ │ ├── Icon-App-40x40@1x.png
│ │ │ │ ├── Icon-App-40x40@2x.png
│ │ │ │ ├── Icon-App-40x40@3x.png
│ │ │ │ ├── Icon-App-60x60@2x.png
│ │ │ │ ├── Icon-App-60x60@3x.png
│ │ │ │ ├── Icon-App-76x76@1x.png
│ │ │ │ ├── Icon-App-76x76@2x.png
│ │ │ │ ├── ItunesArtwork@2x.png
│ │ │ │ └── Icon-App-83.5x83.5@2x.png
│ │ ├── Preview Content
│ │ │ └── Preview Assets.xcassets
│ │ │ │ └── Contents.json
│ │ ├── SampleJoke.json
│ │ ├── Models
│ │ │ └── ChuckNorrisJokes.xcdatamodeld
│ │ │ │ └── ChuckNorrisJokes.xcdatamodel
│ │ │ │ └── contents
│ │ └── Base.lproj
│ │ │ └── LaunchScreen.storyboard
│ ├── ChuckNorrisJokesTests
│ │ ├── TestTranslationResponse.json
│ │ ├── TestJoke.json
│ │ ├── TestTranslatedJoke.json
│ │ └── Info.plist
│ ├── ChuckNorrisJokes.xcodeproj
│ │ └── project.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── ChuckNorrisJokesModel
│ │ └── Info.plist
│ └── challenge
│ ├── final
│ ├── ChuckNorrisJokes
│ │ ├── Assets.xcassets
│ │ │ ├── Contents.json
│ │ │ ├── Colors
│ │ │ │ ├── Contents.json
│ │ │ │ ├── Red.colorset
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── Green.colorset
│ │ │ │ │ └── Contents.json
│ │ │ │ └── Gray.colorset
│ │ │ │ │ └── Contents.json
│ │ │ └── AppIcon.appiconset
│ │ │ │ ├── Icon-App-20x20@1x.png
│ │ │ │ ├── Icon-App-20x20@2x.png
│ │ │ │ ├── Icon-App-20x20@3x.png
│ │ │ │ ├── Icon-App-29x29@1x.png
│ │ │ │ ├── Icon-App-29x29@2x.png
│ │ │ │ ├── Icon-App-29x29@3x.png
│ │ │ │ ├── Icon-App-40x40@1x.png
│ │ │ │ ├── Icon-App-40x40@2x.png
│ │ │ │ ├── Icon-App-40x40@3x.png
│ │ │ │ ├── Icon-App-60x60@2x.png
│ │ │ │ ├── Icon-App-60x60@3x.png
│ │ │ │ ├── Icon-App-76x76@1x.png
│ │ │ │ ├── Icon-App-76x76@2x.png
│ │ │ │ ├── ItunesArtwork@2x.png
│ │ │ │ └── Icon-App-83.5x83.5@2x.png
│ │ ├── Preview Content
│ │ │ └── Preview Assets.xcassets
│ │ │ │ └── Contents.json
│ │ ├── SampleJoke.json
│ │ ├── Models
│ │ │ └── ChuckNorrisJokes.xcdatamodeld
│ │ │ │ └── ChuckNorrisJokes.xcdatamodel
│ │ │ │ └── contents
│ │ └── Base.lproj
│ │ │ └── LaunchScreen.storyboard
│ ├── ChuckNorrisJokesTests
│ │ ├── TestTranslationResponse.json
│ │ ├── TestJoke.json
│ │ ├── TestTranslatedJoke.json
│ │ └── Info.plist
│ ├── ChuckNorrisJokes.xcodeproj
│ │ └── project.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── ChuckNorrisJokesModel
│ │ └── Info.plist
│ └── starter
│ ├── ChuckNorrisJokes
│ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ ├── Colors
│ │ │ ├── Contents.json
│ │ │ ├── Red.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── Green.colorset
│ │ │ │ └── Contents.json
│ │ │ └── Gray.colorset
│ │ │ │ └── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ ├── Icon-App-20x20@1x.png
│ │ │ ├── Icon-App-20x20@2x.png
│ │ │ ├── Icon-App-20x20@3x.png
│ │ │ ├── Icon-App-29x29@1x.png
│ │ │ ├── Icon-App-29x29@2x.png
│ │ │ ├── Icon-App-29x29@3x.png
│ │ │ ├── Icon-App-40x40@1x.png
│ │ │ ├── Icon-App-40x40@2x.png
│ │ │ ├── Icon-App-40x40@3x.png
│ │ │ ├── Icon-App-60x60@2x.png
│ │ │ ├── Icon-App-60x60@3x.png
│ │ │ ├── Icon-App-76x76@1x.png
│ │ │ ├── Icon-App-76x76@2x.png
│ │ │ ├── ItunesArtwork@2x.png
│ │ │ └── Icon-App-83.5x83.5@2x.png
│ ├── Preview Content
│ │ └── Preview Assets.xcassets
│ │ │ └── Contents.json
│ ├── SampleJoke.json
│ ├── Models
│ │ └── ChuckNorrisJokes.xcdatamodeld
│ │ │ └── ChuckNorrisJokes.xcdatamodel
│ │ │ └── contents
│ └── Base.lproj
│ │ └── LaunchScreen.storyboard
│ ├── ChuckNorrisJokesTests
│ ├── TestTranslationResponse.json
│ ├── TestJoke.json
│ ├── TestTranslatedJoke.json
│ └── Info.plist
│ ├── ChuckNorrisJokes.xcodeproj
│ └── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
│ └── ChuckNorrisJokesModel
│ └── Info.plist
├── 08-photo-collage-app
└── Collage
│ ├── Collage
│ ├── Resources
│ │ └── Assets.xcassets
│ │ │ ├── Contents.json
│ │ │ ├── image-1.imageset
│ │ │ ├── image-1.jpg
│ │ │ └── Contents.json
│ │ │ ├── image-2.imageset
│ │ │ ├── image-2.jpg
│ │ │ └── Contents.json
│ │ │ ├── image-3.imageset
│ │ │ ├── image-3.jpg
│ │ │ └── Contents.json
│ │ │ └── image-4.imageset
│ │ │ ├── image-4.jpg
│ │ │ └── Contents.json
│ ├── Preview Content
│ │ └── Preview Assets.xcassets
│ │ │ └── Contents.json
│ ├── Scenes
│ │ └── Collage
│ │ │ ├── AddCollagePhotosView.swift
│ │ │ └── CurrentCollageContainerViewModel.swift
│ ├── Data
│ │ └── Models
│ │ │ └── ImageCollage.swift
│ ├── AppDelegate.swift
│ └── Base.lproj
│ │ └── LaunchScreen.storyboard
│ ├── Resources
│ ├── image-1.jpg
│ ├── image-2.jpg
│ ├── image-3.jpg
│ └── image-4.jpg
│ └── Collage.xcodeproj
│ └── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ ├── IDEWorkspaceChecks.plist
│ └── swiftpm
│ └── Package.resolved
├── 19-testing-combine-code
└── Projects
│ └── ColorCalc
│ ├── ColorCalc
│ ├── Assets.xcassets
│ │ └── Contents.json
│ ├── Preview Content
│ │ └── Preview Assets.xcassets
│ │ │ └── Contents.json
│ └── Base.lproj
│ │ └── LaunchScreen.storyboard
│ ├── ColorCalc.xcodeproj
│ └── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── swiftpm
│ │ └── Package.resolved
│ └── ColorCalcTests
│ ├── Info.plist
│ └── Combine Operators
│ ├── CombineOperatorsTests+ShareReplay.swift
│ └── CombineOperatorsTests+Collect.swift
├── 15-19-bitcoin-average-api-app
└── BitcoinAverageAPIFetcher
│ ├── BitcoinAverageAPIFetcher
│ ├── Resources
│ │ └── Assets.xcassets
│ │ │ └── Contents.json
│ ├── Preview Content
│ │ └── Preview Assets.xcassets
│ │ │ └── Contents.json
│ ├── Networking
│ │ └── Dependencies.swift
│ ├── Reusables
│ │ ├── Formatters
│ │ │ ├── NumberFormatters.swift
│ │ │ └── DateFormatters.swift
│ │ ├── Extensions
│ │ │ └── BitcoinPrice+UpdatedAgoText.swift
│ │ └── Views
│ │ │ ├── TimestampBadge.swift
│ │ │ └── ShitcoinFilterListItem.swift
│ ├── Data
│ │ └── State
│ │ │ ├── AppState.swift
│ │ │ └── SettingsState.swift
│ ├── Scenes
│ │ └── Settings
│ │ │ ├── SettingsView.swift
│ │ │ ├── SettingsContainerView.swift
│ │ │ └── SettingsViewModel.swift
│ ├── AppDelegate.swift
│ └── Base.lproj
│ │ └── LaunchScreen.storyboard
│ └── BitcoinAverageAPIFetcher.xcodeproj
│ └── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ ├── IDEWorkspaceChecks.plist
│ └── swiftpm
│ └── Package.resolved
├── chapters-2-5
└── Playgrounds
│ └── Chapters 2-5.playground
│ ├── Pages
│ ├── 02 - Subjects.xcplaygroundpage
│ │ └── Sources
│ │ │ ├── CustomError.swift
│ │ │ └── StringSubscriber.swift
│ ├── 02 - Challenge.xcplaygroundpage
│ │ ├── Sources
│ │ │ ├── HandError.swift
│ │ │ ├── PlayingCard+Points.swift
│ │ │ ├── Hand.swift
│ │ │ ├── Deck.swift
│ │ │ ├── PlayingCard.swift
│ │ │ ├── Suit.swift
│ │ │ └── Rank.swift
│ │ └── Contents.swift
│ ├── 03 - Flattening Publishers.xcplaygroundpage
│ │ └── Sources
│ │ │ └── Chatter.swift
│ ├── 02 - Type Erasure.xcplaygroundpage
│ │ └── Contents.swift
│ ├── 03 - Challenge.xcplaygroundpage
│ │ ├── Sources
│ │ │ └── Phone.swift
│ │ └── Contents.swift
│ ├── 03 - Incrementally Transforming Output.xcplaygroundpage
│ │ └── Contents.swift
│ ├── 03 - Mapping Values.xcplaygroundpage
│ │ └── Contents.swift
│ ├── 03 - Mapping Keypaths.xcplaygroundpage
│ │ ├── Contents.swift
│ │ └── Sources
│ │ │ └── Coordinate.swift
│ ├── 04 - Filtering Operators Intro.xcplaygroundpage
│ │ └── Contents.swift
│ ├── 02 - Publishers & Subscribers Intro.xcplaygroundpage
│ │ └── Sources
│ │ │ └── IntSubscriber.swift
│ ├── 04 - Compacting and Ignoring.xcplaygroundpage
│ │ └── Contents.swift
│ ├── 03 - Collecting Values.xcplaygroundpage
│ │ └── Contents.swift
│ ├── 02 - Dynamically Adjusting Demand.xcplaygroundpage
│ │ └── Sources
│ │ │ └── IntSubscriber.swift
│ ├── 05 - Merge.xcplaygroundpage
│ │ └── Contents.swift
│ ├── 03 - TryMap.xcplaygroundpage
│ │ └── Contents.swift
│ ├── 05 - zip.xcplaygroundpage
│ │ └── Contents.swift
│ ├── 03 - Replacing Upstream Output.xcplaygroundpage
│ │ └── Contents.swift
│ ├── 04- Challenge.xcplaygroundpage
│ │ └── Contents.swift
│ ├── 05- combineLatest.xcplaygroundpage
│ │ └── Contents.swift
│ └── 04 - Dropping Values.xcplaygroundpage
│ │ └── Contents.swift
│ ├── Sources
│ └── PlaygroundUtils.swift
│ └── contents.xcplayground
├── 16-error-handling
└── Playgrounds
│ └── MyPlayground.playground
│ ├── Resources
│ ├── hq.jpg
│ ├── lq.jpg
│ └── na.jpg
│ ├── Sources
│ └── Utils.swift
│ ├── contents.xcplayground
│ └── Pages
│ ├── Assign.xcplaygroundpage
│ └── Contents.swift
│ ├── Never.xcplaygroundpage
│ └── Contents.swift
│ └── AssertNoFailure.xcplaygroundpage
│ └── Contents.swift
├── 07-sequence-based-operators
└── Playgrounds
│ └── Chapter7.playground
│ ├── Sources
│ └── Utils.swift
│ └── contents.xcplayground
├── 13-resource-management
└── Playgrounds
│ └── MyPlayground.playground
│ ├── Sources
│ └── Utils.swift
│ ├── contents.xcplayground
│ └── Pages
│ ├── The Share Operator.xcplaygroundpage
│ └── Contents.swift
│ └── The Share Operator Pitfalls.xcplaygroundpage
│ └── Contents.swift
├── 17-schedulers
└── Playgrounds
│ └── MyPlayground.playground
│ ├── Sources
│ └── PlaygroundUtils.swift
│ ├── contents.xcplayground
│ └── Pages
│ ├── Challenge 1 - Stop the Timer.xcplaygroundpage
│ └── Contents.swift
│ └── ImmediateScheduler.xcplaygroundpage
│ └── Contents.swift
├── 18-custom-publishers-and-handling-backpressure
└── Playgrounds
│ └── MyPlayground.playground
│ ├── Sources
│ └── PlaygroundUtils.swift
│ ├── contents.xcplayground
│ └── Pages
│ └── Unwrap Operator.xcplaygroundpage
│ └── Contents.swift
├── Playgrounds.xcworkspace
└── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── 06-time-based-operators
└── Playgrounds
│ └── Chapter 6.playground
│ ├── contents.xcplayground
│ ├── Sources
│ ├── DeltaTime.swift
│ └── Data.swift
│ └── Pages
│ ├── Challenge.xcplaygroundpage
│ ├── Sources
│ │ └── Utils.swift
│ └── Contents.swift
│ ├── Shifting Time.xcplaygroundpage
│ └── Contents.swift
│ ├── Holding Off On Events - Timeout.xcplaygroundpage
│ └── Contents.swift
│ └── Holding Off On Events - Debonuce.xcplaygroundpage
│ └── Contents.swift
└── README.md
/20-building-a-complete-app/NumberFacts/Common/README.md:
--------------------------------------------------------------------------------
1 | # Common
2 |
3 | A description of this package.
4 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/Common/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/NumbersAPIService/README.md:
--------------------------------------------------------------------------------
1 | # NumbersAPIService
2 |
3 | A description of this package.
4 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/TranslationService/README.md:
--------------------------------------------------------------------------------
1 | # TranslationService
2 |
3 | A description of this package.
4 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/NumbersAPIService/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/TranslationService/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 |
--------------------------------------------------------------------------------
/08-photo-collage-app/Collage/Collage/Resources/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/19-testing-combine-code/Projects/ColorCalc/ColorCalc/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/08-photo-collage-app/Collage/Collage/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Assets.xcassets/Colors/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Assets.xcassets/Colors/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/19-testing-combine-code/Projects/ColorCalc/ColorCalc/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/NumberFacts/NumberFacts/Resources/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Assets.xcassets/Colors/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Assets.xcassets/Colors/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/08-photo-collage-app/Collage/Resources/image-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/08-photo-collage-app/Collage/Resources/image-1.jpg
--------------------------------------------------------------------------------
/08-photo-collage-app/Collage/Resources/image-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/08-photo-collage-app/Collage/Resources/image-2.jpg
--------------------------------------------------------------------------------
/08-photo-collage-app/Collage/Resources/image-3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/08-photo-collage-app/Collage/Resources/image-3.jpg
--------------------------------------------------------------------------------
/08-photo-collage-app/Collage/Resources/image-4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/08-photo-collage-app/Collage/Resources/image-4.jpg
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/Common/Sources/SupportedLanguages.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Brian Sipple on 2/20/20.
6 | //
7 |
8 | import Foundation
9 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/NumberFacts/NumberFacts/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/15-19-bitcoin-average-api-app/BitcoinAverageAPIFetcher/BitcoinAverageAPIFetcher/Resources/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/chapters-2-5/Playgrounds/Chapters 2-5.playground/Pages/02 - Subjects.xcplaygroundpage/Sources/CustomError.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public enum CustomError: Error {
4 | case oops
5 | }
6 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/15-19-bitcoin-average-api-app/BitcoinAverageAPIFetcher/BitcoinAverageAPIFetcher/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/final/ChuckNorrisJokesTests/TestTranslationResponse.json:
--------------------------------------------------------------------------------
1 | {
2 | "code": 200,
3 | "lang": "en-es",
4 | "text": ["La barba de Chuck Norris puede escribir 140 wpm."]
5 | }
6 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokesTests/TestTranslationResponse.json:
--------------------------------------------------------------------------------
1 | {
2 | "code": 200,
3 | "lang": "en-es",
4 | "text": ["La barba de Chuck Norris puede escribir 140 wpm."]
5 | }
6 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/NumberFacts/NumberFacts.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/Design/screenshot-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/NumberFacts/Design/screenshot-1.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokesTests/TestTranslationResponse.json:
--------------------------------------------------------------------------------
1 | {
2 | "code": 200,
3 | "lang": "en-es",
4 | "text": ["La barba de Chuck Norris puede escribir 140 wpm."]
5 | }
6 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokesTests/TestTranslationResponse.json:
--------------------------------------------------------------------------------
1 | {
2 | "code": 200,
3 | "lang": "en-es",
4 | "text": ["La barba de Chuck Norris puede escribir 140 wpm."]
5 | }
6 |
--------------------------------------------------------------------------------
/16-error-handling/Playgrounds/MyPlayground.playground/Resources/hq.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/16-error-handling/Playgrounds/MyPlayground.playground/Resources/hq.jpg
--------------------------------------------------------------------------------
/16-error-handling/Playgrounds/MyPlayground.playground/Resources/lq.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/16-error-handling/Playgrounds/MyPlayground.playground/Resources/lq.jpg
--------------------------------------------------------------------------------
/16-error-handling/Playgrounds/MyPlayground.playground/Resources/na.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/16-error-handling/Playgrounds/MyPlayground.playground/Resources/na.jpg
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/Common/Sources/Models/NumberFact/NumberFact+Decoder.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | extension NumberFact {
4 | public enum Decoder {
5 | public static let `default` = JSONDecoder()
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/NumbersAPIService/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | import NumbersAPIServiceTests
4 |
5 | var tests = [XCTestCaseEntry]()
6 | tests += NumbersAPIServiceTests.__allTests()
7 |
8 | XCTMain(tests)
9 |
--------------------------------------------------------------------------------
/08-photo-collage-app/Collage/Collage.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/16-error-handling/Playgrounds/MyPlayground.playground/Sources/Utils.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 |
4 | public func demo(describing description: String, action: () -> Void) {
5 | print("\n--- Example of: \(description) ---")
6 | action()
7 | }
8 |
--------------------------------------------------------------------------------
/07-sequence-based-operators/Playgrounds/Chapter7.playground/Sources/Utils.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 |
4 | public func demo(describing description: String, action: () -> Void) {
5 | print("\n--- Example of: \(description) ---")
6 | action()
7 | }
8 |
--------------------------------------------------------------------------------
/13-resource-management/Playgrounds/MyPlayground.playground/Sources/Utils.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 |
4 | public func demo(describing description: String, action: () -> Void) {
5 | print("\n--- Example of: \(description) ---")
6 | action()
7 | }
8 |
--------------------------------------------------------------------------------
/17-schedulers/Playgrounds/MyPlayground.playground/Sources/PlaygroundUtils.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 |
4 | public func demo(describing description: String, action: () -> Void) {
5 | print("\n--- Example of: \(description) ---")
6 | action()
7 | }
8 |
--------------------------------------------------------------------------------
/chapters-2-5/Playgrounds/Chapters 2-5.playground/Sources/PlaygroundUtils.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 |
4 | public func demo(describing description: String, action: () -> Void) {
5 | print("\n--- Example of: \(description) ---")
6 | action()
7 | }
8 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/Common/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/19-testing-combine-code/Projects/ColorCalc/ColorCalc.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/Playgrounds/NumbersAPI.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/08-photo-collage-app/Collage/Collage/Resources/Assets.xcassets/image-1.imageset/image-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/08-photo-collage-app/Collage/Collage/Resources/Assets.xcassets/image-1.imageset/image-1.jpg
--------------------------------------------------------------------------------
/08-photo-collage-app/Collage/Collage/Resources/Assets.xcassets/image-2.imageset/image-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/08-photo-collage-app/Collage/Collage/Resources/Assets.xcassets/image-2.imageset/image-2.jpg
--------------------------------------------------------------------------------
/08-photo-collage-app/Collage/Collage/Resources/Assets.xcassets/image-3.imageset/image-3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/08-photo-collage-app/Collage/Collage/Resources/Assets.xcassets/image-3.imageset/image-3.jpg
--------------------------------------------------------------------------------
/08-photo-collage-app/Collage/Collage/Resources/Assets.xcassets/image-4.imageset/image-4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/08-photo-collage-app/Collage/Collage/Resources/Assets.xcassets/image-4.imageset/image-4.jpg
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/Common/Sources/Models/NumberFact/NumberFact+CoreDataClass.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import CoreData
3 |
4 | @objc(NumberFact)
5 | public class NumberFact: NSManagedObject {
6 |
7 | }
8 |
9 | extension NumberFact: Identifiable {}
10 |
--------------------------------------------------------------------------------
/15-19-bitcoin-average-api-app/BitcoinAverageAPIFetcher/BitcoinAverageAPIFetcher.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/18-custom-publishers-and-handling-backpressure/Playgrounds/MyPlayground.playground/Sources/PlaygroundUtils.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 |
4 | public func demo(describing description: String, action: () -> Void) {
5 | print("\n--- Example of: \(description) ---")
6 | action()
7 | }
8 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/NumberFacts/NumberFacts/Preview Content/Preivew Data/PreviewData.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PreviewData.swift
3 | // NumberFacts
4 | //
5 | // Created by CypherPoet on 2/19/20.
6 | // ✌️
7 | //
8 |
9 | import Foundation
10 |
11 | enum PreviewData {}
12 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png
--------------------------------------------------------------------------------
/Playgrounds.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/07-sequence-based-operators/Playgrounds/Chapter7.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CypherPoet/book--combine-asynchronous-programming-with-swift/HEAD/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/NumberFacts.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/08-photo-collage-app/Collage/Collage.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/19-testing-combine-code/Projects/ColorCalc/ColorCalc.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/13-resource-management/Playgrounds/MyPlayground.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/chapters-2-5/Playgrounds/Chapters 2-5.playground/Pages/02 - Challenge.xcplaygroundpage/Sources/HandError.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 |
4 | public enum HandError: Error, CustomStringConvertible {
5 | case busted
6 |
7 | public var description: String {
8 | switch self {
9 | case .busted:
10 | return "Busted!"
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/NumberFacts/NumberFacts/Data/NumberFacts.xcdatamodeld/.xccurrentversion:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | _XCCurrentVersionName
6 | NumberFacts.xcdatamodel
7 |
8 |
9 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/15-19-bitcoin-average-api-app/BitcoinAverageAPIFetcher/BitcoinAverageAPIFetcher.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/18-custom-publishers-and-handling-backpressure/Playgrounds/MyPlayground.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/16-error-handling/Playgrounds/MyPlayground.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/NumberFacts/NumberFacts/Data/CoreDataManager+Utils.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CoreDataManager+Utils.swift
3 | // NumberFacts
4 | //
5 | // Created by CypherPoet on 2/18/20.
6 | // ✌️
7 | //
8 |
9 | import Foundation
10 | import CypherPoetCoreDataKit_CoreDataManager
11 |
12 |
13 | extension CoreDataManager {
14 | static let shared = CoreDataManager(
15 | managedObjectModelName: "NumberFacts"
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/chapters-2-5/Playgrounds/Chapters 2-5.playground/Pages/03 - Flattening Publishers.xcplaygroundpage/Sources/Chatter.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import Combine
3 |
4 |
5 | public struct Chatter {
6 | public let name: String
7 | public let message: CurrentValueSubject
8 |
9 |
10 | public init(name: String, message: String) {
11 | self.name = name
12 | self.message = CurrentValueSubject(message)
13 | }
14 | }
15 |
16 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/Common/Sources/Models/NumberFact/NumberFact+Category.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 |
4 | extension NumberFact {
5 |
6 | public enum Category: String {
7 | case math
8 | case trivia
9 | case date
10 | case year
11 | }
12 | }
13 |
14 |
15 | extension NumberFact.Category: CaseIterable {}
16 | extension NumberFact.Category: Identifiable {
17 | public var id: String { rawValue }
18 | }
19 |
--------------------------------------------------------------------------------
/08-photo-collage-app/Collage/Collage/Resources/Assets.xcassets/image-1.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "image-1.jpg",
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-photo-collage-app/Collage/Collage/Resources/Assets.xcassets/image-2.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "image-2.jpg",
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-photo-collage-app/Collage/Collage/Resources/Assets.xcassets/image-3.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "image-3.jpg",
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-photo-collage-app/Collage/Collage/Resources/Assets.xcassets/image-4.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "image-4.jpg",
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 | }
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/NumbersAPIService/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "CypherPoetNetStack",
6 | "repositoryURL": "https://github.com/CypherPoet/CypherPoetNetStack.git",
7 | "state": {
8 | "branch": null,
9 | "revision": "b5dbd717cd51726ccba1041ee5981680aaeff95f",
10 | "version": "0.0.28"
11 | }
12 | }
13 | ]
14 | },
15 | "version": 1
16 | }
17 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/final/ChuckNorrisJokesTests/TestJoke.json:
--------------------------------------------------------------------------------
1 | {
2 | "categories": [
3 | "dev"
4 | ],
5 | "created_at": "2016-05-01 10:51:41.584544",
6 | "icon_url": "https:\/\/assets.chucknorris.host\/img\/avatar\/chuck-norris.png",
7 | "id": "ag_6paerrkg-mxfjjqw4ba",
8 | "updated_at": "2016-05-01 10:51:41.584544",
9 | "url": "https:\/\/api.chucknorris.io\/jokes\/ag_6paerrkg-mxfjjqw4ba",
10 | "value": "Chuck Norris's beard can type 140 wpm."
11 | }
12 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokesTests/TestJoke.json:
--------------------------------------------------------------------------------
1 | {
2 | "categories": [
3 | "dev"
4 | ],
5 | "created_at": "2016-05-01 10:51:41.584544",
6 | "icon_url": "https:\/\/assets.chucknorris.host\/img\/avatar\/chuck-norris.png",
7 | "id": "ag_6paerrkg-mxfjjqw4ba",
8 | "updated_at": "2016-05-01 10:51:41.584544",
9 | "url": "https:\/\/api.chucknorris.io\/jokes\/ag_6paerrkg-mxfjjqw4ba",
10 | "value": "Chuck Norris's beard can type 140 wpm."
11 | }
12 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/SampleJoke.json:
--------------------------------------------------------------------------------
1 | {
2 | "categories": [
3 | "dev"
4 | ],
5 | "created_at": "2016-05-01 10:51:41.584544",
6 | "icon_url": "https:\/\/assets.chucknorris.host\/img\/avatar\/chuck-norris.png",
7 | "id": "mgdb9q1wqb6_gurzp_5bga",
8 | "updated_at": "2016-05-01 10:51:41.584544",
9 | "url": "https:\/\/api.chucknorris.io\/jokes\/mgdb9q1wqb6_gurzp_5bga",
10 | "value": "Chuck Norris writes code that optimizes itself."
11 | }
12 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/SampleJoke.json:
--------------------------------------------------------------------------------
1 | {
2 | "categories": [
3 | "dev"
4 | ],
5 | "created_at": "2016-05-01 10:51:41.584544",
6 | "icon_url": "https:\/\/assets.chucknorris.host\/img\/avatar\/chuck-norris.png",
7 | "id": "mgdb9q1wqb6_gurzp_5bga",
8 | "updated_at": "2016-05-01 10:51:41.584544",
9 | "url": "https:\/\/api.chucknorris.io\/jokes\/mgdb9q1wqb6_gurzp_5bga",
10 | "value": "Chuck Norris writes code that optimizes itself."
11 | }
12 |
--------------------------------------------------------------------------------
/chapters-2-5/Playgrounds/Chapters 2-5.playground/Pages/02 - Challenge.xcplaygroundpage/Sources/PlayingCard+Points.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 |
4 | extension PlayingCard {
5 |
6 | public var points: Int {
7 | switch self.rank {
8 | case .jack,
9 | .queen,
10 | .king:
11 | return 10
12 | case .ace:
13 | return 11
14 | default:
15 | return self.rank.rawValue
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokesTests/TestJoke.json:
--------------------------------------------------------------------------------
1 | {
2 | "categories": [
3 | "dev"
4 | ],
5 | "created_at": "2016-05-01 10:51:41.584544",
6 | "icon_url": "https:\/\/assets.chucknorris.host\/img\/avatar\/chuck-norris.png",
7 | "id": "ag_6paerrkg-mxfjjqw4ba",
8 | "updated_at": "2016-05-01 10:51:41.584544",
9 | "url": "https:\/\/api.chucknorris.io\/jokes\/ag_6paerrkg-mxfjjqw4ba",
10 | "value": "Chuck Norris's beard can type 140 wpm."
11 | }
12 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokesTests/TestJoke.json:
--------------------------------------------------------------------------------
1 | {
2 | "categories": [
3 | "dev"
4 | ],
5 | "created_at": "2016-05-01 10:51:41.584544",
6 | "icon_url": "https:\/\/assets.chucknorris.host\/img\/avatar\/chuck-norris.png",
7 | "id": "ag_6paerrkg-mxfjjqw4ba",
8 | "updated_at": "2016-05-01 10:51:41.584544",
9 | "url": "https:\/\/api.chucknorris.io\/jokes\/ag_6paerrkg-mxfjjqw4ba",
10 | "value": "Chuck Norris's beard can type 140 wpm."
11 | }
12 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/SampleJoke.json:
--------------------------------------------------------------------------------
1 | {
2 | "categories": [
3 | "dev"
4 | ],
5 | "created_at": "2016-05-01 10:51:41.584544",
6 | "icon_url": "https:\/\/assets.chucknorris.host\/img\/avatar\/chuck-norris.png",
7 | "id": "mgdb9q1wqb6_gurzp_5bga",
8 | "updated_at": "2016-05-01 10:51:41.584544",
9 | "url": "https:\/\/api.chucknorris.io\/jokes\/mgdb9q1wqb6_gurzp_5bga",
10 | "value": "Chuck Norris writes code that optimizes itself."
11 | }
12 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/SampleJoke.json:
--------------------------------------------------------------------------------
1 | {
2 | "categories": [
3 | "dev"
4 | ],
5 | "created_at": "2016-05-01 10:51:41.584544",
6 | "icon_url": "https:\/\/assets.chucknorris.host\/img\/avatar\/chuck-norris.png",
7 | "id": "mgdb9q1wqb6_gurzp_5bga",
8 | "updated_at": "2016-05-01 10:51:41.584544",
9 | "url": "https:\/\/api.chucknorris.io\/jokes\/mgdb9q1wqb6_gurzp_5bga",
10 | "value": "Chuck Norris writes code that optimizes itself."
11 | }
12 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/final/ChuckNorrisJokesTests/TestTranslatedJoke.json:
--------------------------------------------------------------------------------
1 | {
2 | "categories": [
3 | "dev"
4 | ],
5 | "created_at": "2016-05-01 10:51:41.584544",
6 | "icon_url": "https:\/\/assets.chucknorris.host\/img\/avatar\/chuck-norris.png",
7 | "id": "ag_6paerrkg-mxfjjqw4ba",
8 | "updated_at": "2016-05-01 10:51:41.584544",
9 | "url": "https:\/\/api.chucknorris.io\/jokes\/ag_6paerrkg-mxfjjqw4ba",
10 | "value": "La barba de Chuck Norris puede escribir 140 wpm."
11 | }
12 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokesTests/TestTranslatedJoke.json:
--------------------------------------------------------------------------------
1 | {
2 | "categories": [
3 | "dev"
4 | ],
5 | "created_at": "2016-05-01 10:51:41.584544",
6 | "icon_url": "https:\/\/assets.chucknorris.host\/img\/avatar\/chuck-norris.png",
7 | "id": "ag_6paerrkg-mxfjjqw4ba",
8 | "updated_at": "2016-05-01 10:51:41.584544",
9 | "url": "https:\/\/api.chucknorris.io\/jokes\/ag_6paerrkg-mxfjjqw4ba",
10 | "value": "La barba de Chuck Norris puede escribir 140 wpm."
11 | }
12 |
--------------------------------------------------------------------------------
/08-photo-collage-app/Collage/Collage.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": "8b2d7eb75433ed4b8ad8bc9ff2a43a06dcf4bbe2",
10 | "version": "0.0.18"
11 | }
12 | }
13 | ]
14 | },
15 | "version": 1
16 | }
17 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokesTests/TestTranslatedJoke.json:
--------------------------------------------------------------------------------
1 | {
2 | "categories": [
3 | "dev"
4 | ],
5 | "created_at": "2016-05-01 10:51:41.584544",
6 | "icon_url": "https:\/\/assets.chucknorris.host\/img\/avatar\/chuck-norris.png",
7 | "id": "ag_6paerrkg-mxfjjqw4ba",
8 | "updated_at": "2016-05-01 10:51:41.584544",
9 | "url": "https:\/\/api.chucknorris.io\/jokes\/ag_6paerrkg-mxfjjqw4ba",
10 | "value": "La barba de Chuck Norris puede escribir 140 wpm."
11 | }
12 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokesTests/TestTranslatedJoke.json:
--------------------------------------------------------------------------------
1 | {
2 | "categories": [
3 | "dev"
4 | ],
5 | "created_at": "2016-05-01 10:51:41.584544",
6 | "icon_url": "https:\/\/assets.chucknorris.host\/img\/avatar\/chuck-norris.png",
7 | "id": "ag_6paerrkg-mxfjjqw4ba",
8 | "updated_at": "2016-05-01 10:51:41.584544",
9 | "url": "https:\/\/api.chucknorris.io\/jokes\/ag_6paerrkg-mxfjjqw4ba",
10 | "value": "La barba de Chuck Norris puede escribir 140 wpm."
11 | }
12 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Assets.xcassets/Colors/Red.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" : "1.000",
13 | "alpha" : "1.000",
14 | "blue" : "0.474",
15 | "green" : "0.493"
16 | }
17 | }
18 | }
19 | ]
20 | }
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Assets.xcassets/Colors/Red.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" : "1.000",
13 | "alpha" : "1.000",
14 | "blue" : "0.474",
15 | "green" : "0.493"
16 | }
17 | }
18 | }
19 | ]
20 | }
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/NumberFacts.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
12 |
13 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/15-19-bitcoin-average-api-app/BitcoinAverageAPIFetcher/BitcoinAverageAPIFetcher/Networking/Dependencies.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Dependencies.swift
3 | // BitcoinAverageAPIFetcher
4 | //
5 | // Created by CypherPoet on 11/10/19.
6 | // ✌️
7 | //
8 |
9 | import Foundation
10 | import SatoshiVSKit
11 |
12 |
13 | enum Dependencies {
14 | static let bitcoinAverageAPIService = BitcoinAverageAPIService(
15 | queue: DispatchQueue(label: "BitcoinAverageAPI", qos: .userInitiated, attributes: [.concurrent])
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Assets.xcassets/Colors/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" : "display-p3",
11 | "components" : {
12 | "red" : "0.200",
13 | "alpha" : "1.000",
14 | "blue" : "0.000",
15 | "green" : "0.800"
16 | }
17 | }
18 | }
19 | ]
20 | }
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Assets.xcassets/Colors/Red.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" : "1.000",
13 | "alpha" : "1.000",
14 | "blue" : "0.474",
15 | "green" : "0.493"
16 | }
17 | }
18 | }
19 | ]
20 | }
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Assets.xcassets/Colors/Red.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" : "1.000",
13 | "alpha" : "1.000",
14 | "blue" : "0.474",
15 | "green" : "0.493"
16 | }
17 | }
18 | }
19 | ]
20 | }
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Assets.xcassets/Colors/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" : "display-p3",
11 | "components" : {
12 | "red" : "0.200",
13 | "alpha" : "1.000",
14 | "blue" : "0.000",
15 | "green" : "0.800"
16 | }
17 | }
18 | }
19 | ]
20 | }
--------------------------------------------------------------------------------
/19-testing-combine-code/Projects/ColorCalc/ColorCalc.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "CypherPoetCombineKit",
6 | "repositoryURL": "https://github.com/CypherPoet/CypherPoetCombineKit.git",
7 | "state": {
8 | "branch": null,
9 | "revision": "72bf0a79055af30e2975bcf4e0f3c6b5d51fe429",
10 | "version": "0.0.3"
11 | }
12 | }
13 | ]
14 | },
15 | "version": 1
16 | }
17 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Assets.xcassets/Colors/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" : "display-p3",
11 | "components" : {
12 | "red" : "0.200",
13 | "alpha" : "1.000",
14 | "blue" : "0.000",
15 | "green" : "0.800"
16 | }
17 | }
18 | }
19 | ]
20 | }
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Assets.xcassets/Colors/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" : "display-p3",
11 | "components" : {
12 | "red" : "0.200",
13 | "alpha" : "1.000",
14 | "blue" : "0.000",
15 | "green" : "0.800"
16 | }
17 | }
18 | }
19 | ]
20 | }
--------------------------------------------------------------------------------
/chapters-2-5/Playgrounds/Chapters 2-5.playground/Pages/02 - Challenge.xcplaygroundpage/Sources/Hand.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 |
4 | public typealias Hand = [PlayingCard]
5 |
6 | public extension Hand {
7 |
8 | var points: Int {
9 | reduce(0) { (accumulatedPoints, card) in
10 | accumulatedPoints + card.points
11 | }
12 | }
13 |
14 | var cardString: String {
15 | map { "\($0.rank)\($0.suit)" }.joined(separator: ", ")
16 | }
17 |
18 | var isBusted: Bool { points > 21 }
19 | }
20 |
--------------------------------------------------------------------------------
/17-schedulers/Playgrounds/MyPlayground.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/06-time-based-operators/Playgrounds/Chapter 6.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/NumberFacts/NumberFacts/Preview Content/Preivew Data/PreviewData+AppStores.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PreviewData+AppStores.swift
3 | // NumberFacts
4 | //
5 | // Created by CypherPoet on 2/19/20.
6 | // ✌️
7 | //
8 |
9 | import Foundation
10 |
11 |
12 | extension PreviewData {
13 |
14 | enum AppStores {
15 |
16 | static let `default`: AppStore = {
17 | AppStore(
18 | initialState: AppState(),
19 | appReducer: appReducer
20 | )
21 | }()
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/15-19-bitcoin-average-api-app/BitcoinAverageAPIFetcher/BitcoinAverageAPIFetcher/Reusables/Formatters/NumberFormatters.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NumberFormatters.swift
3 | // BitcoinAverageAPIFetcher
4 | //
5 | // Created by CypherPoet on 11/11/19.
6 | // ✌️
7 | //
8 |
9 | import Foundation
10 |
11 |
12 | enum NumberFormatters {
13 | static let priceReading: NumberFormatter = {
14 | let formatter = NumberFormatter()
15 |
16 | formatter.numberStyle = .decimal
17 | formatter.maximumFractionDigits = 10
18 |
19 | return formatter
20 | }()
21 | }
22 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/NumbersAPIService/Sources/NumbersAPIService.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import CypherPoetNetStack
3 |
4 |
5 | public final class NumbersAPIService {
6 | public var session: URLSession
7 | public var apiQueue: DispatchQueue
8 |
9 | public init(
10 | session: URLSession = .shared,
11 | queue: DispatchQueue = DispatchQueue(label: "NumbersAPIService", qos: .userInitiated)
12 | ) {
13 | self.session = session
14 | self.apiQueue = queue
15 | }
16 | }
17 |
18 |
19 | extension NumbersAPIService: NumbersAPIServicing {}
20 |
--------------------------------------------------------------------------------
/chapters-2-5/Playgrounds/Chapters 2-5.playground/Pages/02 - Type Erasure.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | //: [Previous](@previous)
2 |
3 | import Foundation
4 | import Combine
5 |
6 |
7 | demo(describing: "Type Erasure") {
8 | var subscriptions = Set()
9 | let subject = PassthroughSubject()
10 | let publisher = subject.eraseToAnyPublisher()
11 |
12 |
13 | publisher
14 | .sink(receiveValue: { print($0) })
15 | .store(in: &subscriptions)
16 |
17 | subject.send("⚡️")
18 | subject.send("🦄")
19 |
20 | }
21 |
22 | //: [Next](@next)
23 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/NumberFacts/NumberFacts/App/CurrentApplication.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CurrentApplication.swift
3 | // NumberFacts
4 | //
5 | // Created by CypherPoet on 2/18/20.
6 | // ✌️
7 | //
8 |
9 | import Foundation
10 | import Common
11 | import CypherPoetCoreDataKit_CoreDataManager
12 |
13 |
14 | struct CurrentApplication {
15 | var coreDataManager: CoreDataManager
16 | var defaultLanguage: Language
17 | }
18 |
19 |
20 | var CurrentApp = CurrentApplication(
21 | coreDataManager: .shared,
22 | defaultLanguage: Language(code: Locale.current.languageCode ?? "") ?? .english
23 | )
24 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/NumberFacts/NumberFacts/Reusables/Extensions/NumberFact+errorFact.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NumberFact+errorFact.swift
3 | // NumberFacts
4 | //
5 | // Created by CypherPoet on 2/21/20.
6 | // ✌️
7 | //
8 |
9 | import Foundation
10 | import NumbersAPIService
11 | import Common
12 |
13 |
14 | extension NumberFact {
15 | static let errorFactPayload: NumbersAPIServicing.NumberFactPayload = (
16 | text: "An error occurred while fetching number facts. Some software uses \"-1\" as a code to represent errors.",
17 | number: -1,
18 | category: .trivia
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/TranslationService/Sources/TranslationAPIService.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import CypherPoetNetStack
3 |
4 |
5 | public final class TranslationAPIService {
6 | public var session: URLSession
7 | public var apiQueue: DispatchQueue
8 |
9 | public init(
10 | session: URLSession = .shared,
11 | queue: DispatchQueue = DispatchQueue(label: "TranslationAPIService", qos: .userInitiated)
12 | ) {
13 | self.session = session
14 | self.apiQueue = queue
15 | }
16 | }
17 |
18 |
19 | extension TranslationAPIService: TranslationAPIServicing {}
20 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/NumbersAPIService/Tests/NumbersAPIService/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | #if !canImport(ObjectiveC)
2 | import XCTest
3 |
4 | extension NumbersAPIServiceTests {
5 | // DO NOT MODIFY: This is autogenerated, use:
6 | // `swift test --generate-linuxmain`
7 | // to regenerate.
8 | static let __allTests__NumbersAPIServiceTests = [
9 | ("testFetchRandomYearFact", testFetchRandomYearFact),
10 | ]
11 | }
12 |
13 | public func __allTests() -> [XCTestCaseEntry] {
14 | return [
15 | testCase(NumbersAPIServiceTests.__allTests__NumbersAPIServiceTests),
16 | ]
17 | }
18 | #endif
19 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/TranslationService/Sources/TranslationAPIServiceError.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import CypherPoetNetStack
3 |
4 |
5 | public enum TranslationAPIServiceError: Error {
6 | case network(error: NetStackError)
7 | }
8 |
9 |
10 | extension TranslationAPIServiceError {
11 |
12 | public var errorDescription: String? {
13 | switch self {
14 | case .network(let error):
15 | return error.errorDescription
16 | }
17 | }
18 | }
19 |
20 | // MARK: - Error: Identifiable
21 | extension TranslationAPIServiceError: Identifiable {
22 | public var id: String? { errorDescription }
23 | }
24 |
--------------------------------------------------------------------------------
/08-photo-collage-app/Collage/Collage/Scenes/Collage/AddCollagePhotosView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AddCollagePhotosView.swift
3 | // Collage
4 | //
5 | // Created by CypherPoet on 10/28/19.
6 | // ✌️
7 | //
8 |
9 | import SwiftUI
10 |
11 |
12 | struct AddCollagePhotosView: View {
13 | }
14 |
15 |
16 | // MARK: - Body
17 | extension AddCollagePhotosView {
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 AddCollagePhotos_Previews: PreviewProvider {
27 |
28 | static var previews: some View {
29 | AddCollagePhotosView()
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/06-time-based-operators/Playgrounds/Chapter 6.playground/Sources/DeltaTime.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 |
4 | let start = Date()
5 |
6 |
7 | let deltaFormatter: NumberFormatter = {
8 | let formatter = NumberFormatter()
9 |
10 | formatter.negativePrefix = ""
11 | formatter.minimumFractionDigits = 1
12 | formatter.maximumFractionDigits = 1
13 |
14 | return formatter
15 | }()
16 |
17 |
18 |
19 | /// A simple delta time formatter suitable for use in playground pages: start date is initialized every time the page starts running
20 | public var deltaTime: String {
21 | return deltaFormatter.string(for: Date().timeIntervalSince(start))!
22 | }
23 |
--------------------------------------------------------------------------------
/15-19-bitcoin-average-api-app/BitcoinAverageAPIFetcher/BitcoinAverageAPIFetcher/Reusables/Extensions/BitcoinPrice+UpdatedAgoText.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BitcoinPrice+UpdatedAgoText.swift
3 | // BitcoinAverageAPIFetcher
4 | //
5 | // Created by CypherPoet on 11/12/19.
6 | // ✌️
7 | //
8 |
9 | import Foundation
10 | import SatoshiVSKit
11 |
12 |
13 | extension BitcoinPrice {
14 |
15 | func updatedAgoText(offsetFrom currentDate: Date) -> String {
16 | let timeDiff = timestamp - currentDate.timeIntervalSince1970
17 |
18 | return "Last updated \(DateFormatters.priceUpdatedAgo.localizedString(fromTimeInterval: timeDiff))"
19 | }
20 | }
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/08-photo-collage-app/Collage/Collage/Data/Models/ImageCollage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImageCollage.swift
3 | // Collage
4 | //
5 | // Created by CypherPoet on 10/28/19.
6 | // ✌️
7 | //
8 |
9 | import SwiftUI
10 |
11 |
12 | struct ImageCollage {
13 | let id = UUID()
14 |
15 | var name: String
16 | var processedImage: UIImage?
17 | }
18 |
19 |
20 |
21 | extension ImageCollage: Identifiable {}
22 |
23 | extension ImageCollage: Hashable {
24 | func hash(into hasher: inout Hasher) {
25 | hasher.combine(name)
26 | }
27 | }
28 |
29 |
30 | #if DEBUG
31 |
32 | let sampleCollage = ImageCollage(
33 | name: "Mass Effect",
34 | processedImage: nil
35 | )
36 |
37 | #endif
38 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/Playgrounds/NumbersAPI.playground/Contents.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import PlaygroundSupport
3 |
4 | PlaygroundPage.current.needsIndefiniteExecution = true
5 |
6 | let url = URL(string: "http://numbersapi.com/random/math")!
7 |
8 |
9 | URLSession.shared.dataTask(with: url) { (data, response, error) in
10 | print(String(data: data!, encoding: .utf8)!)
11 |
12 | guard let response = response as? HTTPURLResponse else { return }
13 |
14 | print(response.value(forHTTPHeaderField: "X-Numbers-API-Number"))
15 | print(response.value(forHTTPHeaderField: "X-Numbers-API-Type"))
16 |
17 | PlaygroundPage.current.finishExecution()
18 | }
19 | .resume()
20 |
--------------------------------------------------------------------------------
/chapters-2-5/Playgrounds/Chapters 2-5.playground/Pages/02 - Challenge.xcplaygroundpage/Sources/Deck.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public struct Deck {
4 | public var cards: [PlayingCard]
5 |
6 |
7 | public init(cards: [PlayingCard]) {
8 | self.cards = cards
9 | }
10 |
11 |
12 | public static func deckOf52() -> Deck {
13 | Deck(cards: makeDeckOf52Cards())
14 | }
15 | }
16 |
17 |
18 | extension Deck {
19 |
20 | private static func makeDeckOf52Cards() -> [PlayingCard] {
21 | Suit.allCases.map { suit in
22 | Rank.allCases.map { rank in
23 | PlayingCard(suit: suit, rank: rank )
24 | }
25 | }
26 | .flatMap { $0 }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/NumberFacts/NumberFacts/Reusables/Extensions/Collection+deleteManagedObjects.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Collection+deleteManagedObjects.swift
3 | // NumberFacts
4 | //
5 | // Created by CypherPoet on 2/24/20.
6 | // ✌️
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 |
12 |
13 | extension Collection where
14 | Element: NSManagedObject,
15 | Index == Int
16 | {
17 |
18 | func delete(at indices: IndexSet) {
19 | indices.forEach { index in
20 | let element = self[index]
21 |
22 | guard let context = element.managedObjectContext else { preconditionFailure() }
23 |
24 | context.delete(element)
25 | }
26 |
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/chapters-2-5/Playgrounds/Chapters 2-5.playground/Pages/02 - Challenge.xcplaygroundpage/Sources/PlayingCard.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public struct PlayingCard {
4 | public let suit: Suit
5 | public let rank: Rank
6 |
7 |
8 | public init(suit: Suit, rank: Rank) {
9 | self.suit = suit
10 | self.rank = rank
11 | }
12 | }
13 |
14 | extension PlayingCard: Equatable {}
15 | extension PlayingCard: Hashable {}
16 |
17 |
18 | extension PlayingCard: Comparable {
19 | public static func < (lhs: PlayingCard, rhs: PlayingCard) -> Bool {
20 | if lhs.rank == rhs.rank {
21 | // Suit is a tie-breaker when the rank matches
22 | return lhs.suit < rhs.suit
23 | } else {
24 | return lhs.rank < rhs.rank
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/chapters-2-5/Playgrounds/Chapters 2-5.playground/Pages/03 - Challenge.xcplaygroundpage/Sources/Phone.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public enum Phone {
4 |
5 | private static let keypad = [
6 | "abc": 1,
7 | "def": 2,
8 | "ghi": 3,
9 | "jkl": 4,
10 | "mno": 5,
11 | "pqr": 6,
12 | "stu": 7,
13 | "vqr": 8,
14 | "wxyz": 9
15 | ]
16 |
17 |
18 | public static func numberFromInput(_ input: Character) -> Int? {
19 | if let number = Int(String(input)), number < 10 {
20 | return number
21 | }
22 |
23 | for letters in Self.keypad.keys {
24 | if letters.contains(input) {
25 | return Self.keypad[letters]
26 | }
27 | }
28 |
29 | return nil
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/chapters-2-5/Playgrounds/Chapters 2-5.playground/Pages/03 - Incrementally Transforming Output.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | //: [Previous](@previous)
2 |
3 | import Foundation
4 | import Combine
5 |
6 |
7 | var subscriptions = Set()
8 |
9 |
10 |
11 | //: `scan` will provide the current value emitted by an upstream
12 | //: publisher to a closure, along with the last value returned by that closure
13 |
14 |
15 | demo(describing: "The `scan` publisher") {
16 | let dailyPriceChanges = (0...30).map { _ in Double.random(in: -100...100) }
17 |
18 | dailyPriceChanges.publisher
19 | .scan(0) { (previousResult, currentValue) in
20 | max(0, previousResult + currentValue)
21 | }
22 | .sink(receiveValue: { _ in })
23 | .store(in: &subscriptions)
24 | }
25 |
26 |
27 | //: [Next](@next)
28 |
--------------------------------------------------------------------------------
/19-testing-combine-code/Projects/ColorCalc/ColorCalcTests/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 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/final/ChuckNorrisJokesTests/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 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokesTests/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 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/NumberFacts/NumberFactsTests/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 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokesTests/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 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokesTests/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 |
--------------------------------------------------------------------------------
/16-error-handling/Playgrounds/MyPlayground.playground/Pages/Assign.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | //: [Previous](@previous)
2 |
3 | import Foundation
4 | import Combine
5 |
6 |
7 | //: ## Assign
8 |
9 | var subscriptions = Set()
10 |
11 |
12 |
13 | class Player {
14 | var name: String = "Unknown"
15 | var xp: Double = 0.0
16 | }
17 |
18 |
19 |
20 | demo(describing: "assign") {
21 | let player = Player()
22 |
23 | print("Player name before assignment: \(player.name)")
24 |
25 | Just("CypherPoet")
26 | // .setFailureType(to: Error.self)
27 | .handleEvents(
28 | receiveCompletion: { _ in print("completion") }
29 | )
30 | .assign(to: \.name, on: player)
31 | .store(in: &subscriptions)
32 |
33 |
34 | print("Player name after assignment: \(player.name)")
35 |
36 | }
37 |
38 | //: [Next](@next)
39 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/final/ChuckNorrisJokesModel/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 | $(CURRENT_PROJECT_VERSION)
21 |
22 |
23 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokesModel/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 | $(CURRENT_PROJECT_VERSION)
21 |
22 |
23 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokesModel/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 | $(CURRENT_PROJECT_VERSION)
21 |
22 |
23 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokesModel/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 | $(CURRENT_PROJECT_VERSION)
21 |
22 |
23 |
--------------------------------------------------------------------------------
/chapters-2-5/Playgrounds/Chapters 2-5.playground/Pages/03 - Mapping Values.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | //: [Previous](@previous)
2 |
3 | //: In addition to collecting values, you’ll often want to transform those values in some way.
4 | //: Combine offers several mapping operators for that purpose
5 |
6 | import Foundation
7 | import Combine
8 |
9 | var cancellables = Set()
10 |
11 |
12 | demo(describing: "The `map` operator") {
13 | let numbers = [1, 1, 2, 3, 5, 8, 13, 21, 34]
14 | let formatter = NumberFormatter()
15 |
16 | formatter.numberStyle = .spellOut
17 |
18 | numbers
19 | .publisher
20 | .map({ formatter.string(from: $0 as NSNumber) ?? "" })
21 | .sink(
22 | receiveCompletion: { print($0) },
23 | receiveValue: { print($0) }
24 | )
25 | .store(in: &cancellables)
26 | }
27 |
28 |
29 | //: [Next](@next)
30 |
--------------------------------------------------------------------------------
/15-19-bitcoin-average-api-app/BitcoinAverageAPIFetcher/BitcoinAverageAPIFetcher/Reusables/Formatters/DateFormatters.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DateFormatters.swift
3 | // BitcoinAverageAPIFetcher
4 | //
5 | // Created by CypherPoet on 11/11/19.
6 | // ✌️
7 | //
8 |
9 | import Foundation
10 |
11 |
12 | enum DateFormatters {
13 | static let priceReadingTime: DateFormatter = {
14 | let formatter = DateFormatter()
15 |
16 | formatter.timeZone = .current
17 | formatter.timeStyle = .medium
18 |
19 | return formatter
20 | }()
21 |
22 |
23 | static let priceReadingTimeBadge: DateFormatter = {
24 | let formatter = DateFormatter()
25 |
26 | formatter.dateStyle = .none
27 | formatter.timeStyle = .short
28 |
29 | return formatter
30 | }()
31 |
32 |
33 | static let priceUpdatedAgo = RelativeDateTimeFormatter()
34 | }
35 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/NumberFacts/NumberFacts/Data/State/AppState.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppState.swift
3 | // NumberFacts
4 | //
5 | // Created by CypherPoet on 2/18/20.
6 | // ✌️
7 | //
8 |
9 |
10 | import Foundation
11 | import Combine
12 | import CypherPoetSwiftUIKit_DataFlowUtils
13 |
14 |
15 | struct AppState {
16 | var numberFactsState = NumberFactsState()
17 | }
18 |
19 |
20 |
21 | //enum AppSideEffect: SideEffect {
22 | //
23 | //}
24 |
25 |
26 |
27 | enum AppAction {
28 | case numberFacts(_ action: NumberFactsAction)
29 | }
30 |
31 |
32 | // MARK: - Reducer
33 | let appReducer: Reducer = Reducer(
34 | reduce: { appState, action in
35 | switch action {
36 | case .numberFacts(let action):
37 | numberFactsReducer.reduce(&appState.numberFactsState, action)
38 | }
39 | }
40 | )
41 |
42 |
43 | typealias AppStore = Store
44 |
45 |
--------------------------------------------------------------------------------
/chapters-2-5/Playgrounds/Chapters 2-5.playground/Pages/03 - Mapping Keypaths.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | //: [Previous](@previous)
2 |
3 | import Foundation
4 | import Combine
5 |
6 |
7 | var subscriptions = Set()
8 |
9 |
10 | demo(describing: "Mapping multiple keypaths") {
11 | let publisher = PassthroughSubject()
12 |
13 | publisher
14 | .map(\.x, \.y)
15 | .sink { (x, y) in
16 | print("""
17 | The coordinate at (\(x), \(y)) is \(Coordinate.quadrantDescriptionOf(x: x, y: y))
18 | """
19 | )
20 | }
21 | .store(in: &subscriptions)
22 |
23 | publisher.send(Coordinate(x: 9, y: 10))
24 | publisher.send(Coordinate(x: 0, y: 10))
25 | publisher.send(Coordinate(x: -1, y: 10))
26 | publisher.send(Coordinate(x: 0, y: 0))
27 | publisher.send(Coordinate(x: 10, y: 0))
28 | }
29 |
30 | //: [Next](@next)
31 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/NumbersAPIService/Sources/NumbersAPIServiceError.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import CypherPoetNetStack
3 |
4 |
5 | public enum NumbersAPIServiceError: LocalizedError {
6 | case network(error: NetStackError)
7 | case parsing(response: HTTPURLResponse, data: Data)
8 | case generic(error: Error)
9 | }
10 |
11 |
12 | extension NumbersAPIServiceError {
13 |
14 | public var errorDescription: String? {
15 | switch self {
16 | case .network(let error):
17 | return error.errorDescription
18 | case .parsing(let response, let data):
19 | return "Unable to make NumberFact from HTTPURLResponse and Data"
20 | case .generic:
21 | return "Unknown error type"
22 | }
23 | }
24 | }
25 |
26 |
27 | // MARK: - Error: Identifiable
28 | extension NumbersAPIServiceError: Identifiable {
29 | public var id: String? { errorDescription }
30 | }
31 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Assets.xcassets/Colors/Gray.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | },
6 | "colors" : [
7 | {
8 | "idiom" : "universal",
9 | "color" : {
10 | "color-space" : "extended-srgb",
11 | "components" : {
12 | "red" : "0.949",
13 | "alpha" : "1.000",
14 | "blue" : "0.969",
15 | "green" : "0.949"
16 | }
17 | }
18 | },
19 | {
20 | "idiom" : "universal",
21 | "appearances" : [
22 | {
23 | "appearance" : "luminosity",
24 | "value" : "dark"
25 | }
26 | ],
27 | "color" : {
28 | "color-space" : "srgb",
29 | "components" : {
30 | "red" : "0.574",
31 | "alpha" : "1.000",
32 | "blue" : "0.574",
33 | "green" : "0.574"
34 | }
35 | }
36 | }
37 | ]
38 | }
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Assets.xcassets/Colors/Gray.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | },
6 | "colors" : [
7 | {
8 | "idiom" : "universal",
9 | "color" : {
10 | "color-space" : "extended-srgb",
11 | "components" : {
12 | "red" : "0.949",
13 | "alpha" : "1.000",
14 | "blue" : "0.969",
15 | "green" : "0.949"
16 | }
17 | }
18 | },
19 | {
20 | "idiom" : "universal",
21 | "appearances" : [
22 | {
23 | "appearance" : "luminosity",
24 | "value" : "dark"
25 | }
26 | ],
27 | "color" : {
28 | "color-space" : "srgb",
29 | "components" : {
30 | "red" : "0.574",
31 | "alpha" : "1.000",
32 | "blue" : "0.574",
33 | "green" : "0.574"
34 | }
35 | }
36 | }
37 | ]
38 | }
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Assets.xcassets/Colors/Gray.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | },
6 | "colors" : [
7 | {
8 | "idiom" : "universal",
9 | "color" : {
10 | "color-space" : "extended-srgb",
11 | "components" : {
12 | "red" : "0.949",
13 | "alpha" : "1.000",
14 | "blue" : "0.969",
15 | "green" : "0.949"
16 | }
17 | }
18 | },
19 | {
20 | "idiom" : "universal",
21 | "appearances" : [
22 | {
23 | "appearance" : "luminosity",
24 | "value" : "dark"
25 | }
26 | ],
27 | "color" : {
28 | "color-space" : "srgb",
29 | "components" : {
30 | "red" : "0.574",
31 | "alpha" : "1.000",
32 | "blue" : "0.574",
33 | "green" : "0.574"
34 | }
35 | }
36 | }
37 | ]
38 | }
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Assets.xcassets/Colors/Gray.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | },
6 | "colors" : [
7 | {
8 | "idiom" : "universal",
9 | "color" : {
10 | "color-space" : "extended-srgb",
11 | "components" : {
12 | "red" : "0.949",
13 | "alpha" : "1.000",
14 | "blue" : "0.969",
15 | "green" : "0.949"
16 | }
17 | }
18 | },
19 | {
20 | "idiom" : "universal",
21 | "appearances" : [
22 | {
23 | "appearance" : "luminosity",
24 | "value" : "dark"
25 | }
26 | ],
27 | "color" : {
28 | "color-space" : "srgb",
29 | "components" : {
30 | "red" : "0.574",
31 | "alpha" : "1.000",
32 | "blue" : "0.574",
33 | "green" : "0.574"
34 | }
35 | }
36 | }
37 | ]
38 | }
--------------------------------------------------------------------------------
/chapters-2-5/Playgrounds/Chapters 2-5.playground/Pages/04 - Filtering Operators Intro.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | //: [Previous](@previous)
2 |
3 | import Foundation
4 | import Combine
5 |
6 | var subscriptions = Set()
7 |
8 |
9 |
10 | demo(describing: "The `filter` operator") {
11 | let numbers = (1...20).map { _ in Int.random(in: 1...100) }
12 |
13 | numbers.publisher
14 | .filter({ $0.isMultiple(of: 2) })
15 | .sink { value in
16 | print("\(value) is even")
17 | }
18 | .store(in: &subscriptions)
19 | }
20 |
21 |
22 |
23 | demo(describing: "The `removeDuplicates` operator") {
24 | let sentence = "Keep it it secret! Keep Keep it safe!"
25 |
26 | sentence
27 | .components(separatedBy: " ")
28 | .publisher
29 | .removeDuplicates()
30 | .sink { print("(sink) Received value: \($0)") }
31 | .store(in: &subscriptions)
32 | }
33 |
34 | //: [Next](@next)
35 |
--------------------------------------------------------------------------------
/chapters-2-5/Playgrounds/Chapters 2-5.playground/Pages/02 - Subjects.xcplaygroundpage/Sources/StringSubscriber.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import Combine
3 |
4 | public enum MyError: Error {
5 | case oops
6 | }
7 |
8 |
9 | public final class StringSubscriber: Subscriber {
10 | public typealias Input = String
11 | public typealias Failure = CustomError
12 |
13 | public init() {}
14 | }
15 |
16 | extension StringSubscriber {
17 |
18 | public func receive(subscription: Subscription) {
19 | subscription.request(.max(2))
20 | }
21 |
22 |
23 | public func receive(_ input: String) -> Subscribers.Demand {
24 | print("String Subscriber -- Recevied input: \(input)")
25 |
26 | return input == "World" ? .max(1) : .none
27 | }
28 |
29 |
30 | public func receive(completion: Subscribers.Completion) {
31 | print("String Subscriber -- Received completion: \(completion)")
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/06-time-based-operators/Playgrounds/Chapter 6.playground/Pages/Challenge.xcplaygroundpage/Sources/Utils.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import Combine
3 |
4 |
5 | let samples: [(TimeInterval, Int)] = [
6 | (0.05, 67),
7 | (0.10, 111),
8 | (0.15, 109),
9 | (0.20, 98),
10 | (0.25, 105),
11 | (0.30, 110),
12 | (0.35, 101),
13 | (1.50, 105),
14 | (1.55, 115),
15 | (2.60, 99),
16 | (2.65, 111),
17 | (2.70, 111),
18 | (2.75, 108),
19 | (2.80, 33),
20 | ]
21 |
22 |
23 | public func feedValues(to subject: S) where S.Output == Int {
24 | var lastDelay: TimeInterval = 0
25 |
26 | for (delay, number) in samples {
27 | lastDelay = delay
28 |
29 | DispatchQueue.main.asyncAfter(deadline: .now() + lastDelay) {
30 | subject.send(number)
31 | }
32 | }
33 |
34 | DispatchQueue.main.asyncAfter(deadline: .now() + lastDelay + 0.5) {
35 | subject.send(completion: .finished)
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/chapters-2-5/Playgrounds/Chapters 2-5.playground/Pages/02 - Publishers & Subscribers Intro.xcplaygroundpage/Sources/IntSubscriber.swift:
--------------------------------------------------------------------------------
1 | import Combine
2 |
3 |
4 | public final class IntSubscriber: Subscriber {
5 | public typealias Input = Int
6 | public typealias Failure = Never
7 |
8 | // var combineIdentifier: CombineIdentifier
9 |
10 |
11 | public init() {}
12 |
13 |
14 | public func receive(subscription: Subscription) {
15 | // subscription.request(.max(3))
16 | subscription.request(.unlimited)
17 | }
18 |
19 | public func receive(_ input: Int) -> Subscribers.Demand {
20 | print("Subscriber received value: \(input)")
21 |
22 | // Tell the publisher that we aren't adjusting the demand after receiving
23 | return .none
24 | }
25 |
26 | public func receive(completion: Subscribers.Completion) {
27 | print("Subscriber received completion: \(completion)")
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/15-19-bitcoin-average-api-app/BitcoinAverageAPIFetcher/BitcoinAverageAPIFetcher/Data/State/AppState.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppState.swift
3 | // BitcoinAverageAPIFetcher
4 | //
5 | // Created by CypherPoet on 11/9/19.
6 | // ✌️
7 | //
8 |
9 | import SwiftUI
10 | import CypherPoetSwiftUIKit
11 |
12 |
13 | struct AppState {
14 | var pricesState = PricesState()
15 | var settingsState = SettingsState()
16 | }
17 |
18 |
19 | enum AppAction {
20 | case prices(_ pricesAction: PricesAction)
21 | case settings(_ settingsAction: SettingsAction)
22 | }
23 |
24 |
25 | //enum AppSideEffect: SideEffect {}
26 |
27 |
28 | // MARK: - Reducer
29 | let appReducer = Reducer { appState, action in
30 | switch action {
31 | case let .prices(action):
32 | pricesReducer.reduce(&appState.pricesState, action)
33 | case let .settings(action):
34 | settingsReducer.reduce(&appState.settingsState, action)
35 | }
36 | }
37 |
38 |
39 | typealias AppStore = Store
40 |
--------------------------------------------------------------------------------
/18-custom-publishers-and-handling-backpressure/Playgrounds/MyPlayground.playground/Pages/Unwrap Operator.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | //: [Previous](@previous)
2 |
3 | import Foundation
4 | import Combine
5 |
6 |
7 | var subscriptions = Set()
8 |
9 |
10 | extension Publisher {
11 |
12 | func unwrap() -> Publishers.CompactMap
13 | where Output == Optional
14 | {
15 | compactMap { $0 }
16 | }
17 | }
18 |
19 |
20 | demo(describing: "Using our custom `unwrap` operator") {
21 | let numbers: [Int?] = [1, 1, 2, 3, nil, nil, 13]
22 |
23 | numbers
24 | .publisher
25 | .unwrap()
26 | .sink(
27 | receiveCompletion: { completion in
28 | print("Received completion: \(completion)")
29 | },
30 | receiveValue: { value in
31 | print("Received value: \(value)")
32 | }
33 | )
34 | .store(in: &subscriptions)
35 | }
36 |
37 | //: [Next](@next)
38 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/NumberFacts/NumberFacts/Preview Content/Preivew Data/PreviewData+NumberFacts.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PreviewData+NumberFacts.swift
3 | // NumberFacts
4 | //
5 | // Created by CypherPoet on 2/20/20.
6 | // ✌️
7 | //
8 |
9 | import Foundation
10 | import Common
11 |
12 |
13 | extension PreviewData {
14 |
15 | enum NumberFacts {
16 |
17 | static let sample1: NumberFact = {
18 | let context = CurrentApp.coreDataManager.mainContext
19 | let numberFact = NumberFact(context: context)
20 |
21 | numberFact.number = 22
22 | numberFact.category = .math
23 | numberFact.text = "408 is the 8^{th} Pell number."
24 | numberFact.currentLanguage = .english
25 | numberFact.translationLanguage = .french
26 | numberFact.translatedText = nil
27 | numberFact.isFavorite = true
28 |
29 | return numberFact
30 | }()
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/chapters-2-5/Playgrounds/Chapters 2-5.playground/Pages/04 - Compacting and Ignoring.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | //: [Previous](@previous)
2 |
3 | import Foundation
4 | import Combine
5 |
6 | var subscriptions = Set()
7 |
8 |
9 | demo(describing: "The `compactMap` operator") {
10 | let charges = ["12.23", "Free", "N/A", "823", "3391", "-10"]
11 |
12 | charges
13 | .publisher
14 | .compactMap(Double.init)
15 | .sink { print($0) }
16 | .store(in: &subscriptions)
17 | }
18 |
19 |
20 | demo(describing: "The `ignoreOutput` operator") {
21 | let numbers = 1...10_000
22 |
23 | numbers
24 | .publisher
25 | .ignoreOutput()
26 | .sink(
27 | receiveCompletion: { completion in
28 | print("(sink) Received completion: \(completion)")
29 | },
30 | receiveValue: { value in
31 | print("(sink) Received value: \(value)")
32 | }
33 | )
34 | .store(in: &subscriptions)
35 | }
36 |
37 | //: [Next](@next)
38 |
--------------------------------------------------------------------------------
/16-error-handling/Playgrounds/MyPlayground.playground/Pages/Never.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | //: [Previous](@previous)
2 |
3 | import Foundation
4 | import Combine
5 |
6 |
7 | //: ## Never
8 |
9 | var subscriptions = Set()
10 |
11 |
12 | enum CustomError: Error {
13 | case ohNo
14 | case oopsieDaisy
15 | }
16 |
17 |
18 | demo(describing: "Setting a failure type for a `Never` stream") {
19 | Just("⚡️")
20 | .setFailureType(to: CustomError.self)
21 | .eraseToAnyPublisher()
22 | .sink(
23 | receiveCompletion: { (completion) in
24 | switch completion {
25 | case .failure(.ohNo):
26 | print("Oh No!")
27 | case .failure(.oopsieDaisy):
28 | print("Whaaa???!!!")
29 | case .finished:
30 | print("Finished successfully!")
31 | }
32 | },
33 | receiveValue: { print($0) }
34 | )
35 | .store(in: &subscriptions)
36 | }
37 |
38 | //: [Next](@next)
39 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/NumberFacts/NumberFacts/Scenes/RootView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RootView.swift
3 | // NumberFacts
4 | //
5 | // Created by CypherPoet on 2/18/20.
6 | // ✌️
7 | //
8 |
9 | import SwiftUI
10 |
11 |
12 | struct RootView {
13 | @Environment(\.managedObjectContext) private var managedObjectContext
14 | @EnvironmentObject var store: AppStore
15 | }
16 |
17 |
18 | // MARK: - View
19 | extension RootView: View {
20 |
21 | var body: some View {
22 | HomeView()
23 | }
24 | }
25 |
26 |
27 | // MARK: - Computeds
28 | extension RootView {
29 | }
30 |
31 |
32 | // MARK: - View Variables
33 | extension RootView {
34 | }
35 |
36 |
37 | // MARK: - Private Helpers
38 | private extension RootView {
39 | }
40 |
41 |
42 |
43 | // MARK: - Preview
44 | struct RootView_Previews: PreviewProvider {
45 |
46 | static var previews: some View {
47 | RootView()
48 | .environmentObject(PreviewData.AppStores.default)
49 | .environment(\.managedObjectContext, CurrentApp.coreDataManager.mainContext)
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/chapters-2-5/Playgrounds/Chapters 2-5.playground/Pages/03 - Collecting Values.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | //: [Previous](@previous)
2 |
3 | import Foundation
4 | import Combine
5 |
6 |
7 | var cancellables = Set()
8 |
9 |
10 | demo(describing: "The `collect` operator") {
11 | let elements = ["🌍", "💨", "🔥", "💦", "🧙♂️"]
12 |
13 | elements
14 | .publisher
15 | .collect()
16 | .sink(
17 | receiveCompletion: { print($0) },
18 | receiveValue: { print($0) }
19 | )
20 | .store(in: &cancellables)
21 |
22 |
23 | elements
24 | .publisher
25 | .collect(2)
26 | .sink(
27 | receiveCompletion: { print($0) },
28 | receiveValue: { print($0) }
29 | )
30 | .store(in: &cancellables)
31 | /// The last value is still emitted as an array. That’s because the upstream publisher completed
32 | /// before collect filled its prescribed buffer, so it sent whatever it had left as an array.
33 | }
34 |
35 |
36 | //: [Next](@next)
37 |
--------------------------------------------------------------------------------
/chapters-2-5/Playgrounds/Chapters 2-5.playground/Pages/02 - Challenge.xcplaygroundpage/Sources/Suit.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public enum Suit: String, CaseIterable {
4 | case spades
5 | case hearts
6 | case diamonds
7 | case clubs
8 | }
9 |
10 |
11 | extension Suit: Comparable {
12 | public static func < (lhs: Suit, rhs: Suit) -> Bool {
13 | switch (lhs, rhs) {
14 | case (_, _) where lhs == rhs:
15 | return false
16 | case (.spades, _),
17 | (.hearts, .diamonds),
18 | (.hearts, .clubs),
19 | (.diamonds, .clubs):
20 | return false
21 | case (_, _):
22 | return true
23 | }
24 | }
25 | }
26 |
27 |
28 | extension Suit: CustomStringConvertible {
29 |
30 | public var description: String {
31 | switch self {
32 | case .spades:
33 | return "♠️"
34 | case .hearts:
35 | return "♥️"
36 | case .diamonds:
37 | return "♦️"
38 | case .clubs:
39 | return "♣️"
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/chapters-2-5/Playgrounds/Chapters 2-5.playground/Pages/02 - Dynamically Adjusting Demand.xcplaygroundpage/Sources/IntSubscriber.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import Combine
3 |
4 | //
5 | //public final class IntSubscriber: Subscriber {
6 | // public typealias Input = Int
7 | // public typealias Failure = Never
8 | //
9 | // public init() {}
10 | //}
11 | //
12 | //
13 | //extension IntSubscriber {
14 | //
15 | // public func receive(subscription: Subscription) {
16 | // subscription.request(.max(2))
17 | // }
18 | //
19 | //
20 | // public func receive(_ input: Input) -> Subscribers.Demand {
21 | // print("(Subscriber) Received Input: \(input)")
22 | //
23 | // switch input {
24 | // case 1:
25 | // return .max(2)
26 | // case 3:
27 | // return .max(1)
28 | // default:
29 | // return .none
30 | // }
31 | // }
32 | //
33 | //
34 | // public func receive(completion: Subscribers.Completion) {
35 | // print("(Subscriber) Received completion: \(completion)")
36 | // }
37 | //
38 | //}
39 |
--------------------------------------------------------------------------------
/08-photo-collage-app/Collage/Collage/Scenes/Collage/CurrentCollageContainerViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CurrentCollageContainerViewModel.swift
3 | // Collage
4 | //
5 | // Created by CypherPoet on 11/3/19.
6 | // ✌️
7 | //
8 |
9 | import SwiftUI
10 | import Combine
11 |
12 |
13 | final class CurrentCollageContainerViewModel: ObservableObject {
14 | private var subscriptions = Set()
15 |
16 |
17 | @Published var isPhotoWriterAuthorized: Bool = false
18 |
19 |
20 | init() {
21 | setupSubscribers()
22 | }
23 | }
24 |
25 |
26 |
27 | // MARK: - Publishers
28 | extension CurrentCollageContainerViewModel {
29 | }
30 |
31 |
32 |
33 |
34 | // MARK: - Private Helpers
35 | private extension CurrentCollageContainerViewModel {
36 |
37 | func setupSubscribers() {
38 | PhotoWriter.isAuthorized
39 | .receive(on: DispatchQueue.main)
40 | // .print("CurrentCollageContainerViewModel - PhotoWriter.isAuthorized")
41 | .assign(to: \.isPhotoWriterAuthorized, on: self)
42 | .store(in: &subscriptions)
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/16-error-handling/Playgrounds/MyPlayground.playground/Pages/AssertNoFailure.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | //: [Previous](@previous)
2 |
3 | import Foundation
4 | import Combine
5 |
6 |
7 | //: ## AssertNoFailure
8 |
9 | //: The `assertNoFailure` operator is useful when you want to protect
10 | //: yourself during development and confirm a publisher can't finish with a failure event.
11 | //:
12 | //: It doesn't prevent a failure event from being emitted by the upstream.
13 | //: However, it will crash with a fatalError if it detects an error, which
14 | //: gives you a good incentive to fix it in development.
15 |
16 |
17 | var subscriptions = Set()
18 |
19 |
20 | enum MyError: Error {
21 | case oops
22 | }
23 |
24 |
25 | demo(describing: "assertNoFailure") {
26 | Just("🚀")
27 | .setFailureType(to: MyError.self)
28 | // .tryMap { _ in throw MyError.oops } // 📝 Uncomment this code to fail
29 | .assertNoFailure()
30 | .sink(
31 | receiveValue: { print($0) }
32 | )
33 | .store(in: &subscriptions)
34 | }
35 |
36 | //: [Next](@next)
37 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/NumberFacts/NumberFactsTests/NumberFactsTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NumberFactsTests.swift
3 | // NumberFactsTests
4 | //
5 | // Created by Brian Sipple on 2/14/20.
6 | // Copyright © 2020 CypherPoet. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import NumberFacts
11 |
12 | class NumberFactsTests: XCTestCase {
13 |
14 | override func setUp() {
15 | // Put setup code here. This method is called before the invocation of each test method in the class.
16 | }
17 |
18 | override func tearDown() {
19 | // Put teardown code here. This method is called after the invocation of each test method in the class.
20 | }
21 |
22 | func testExample() {
23 | // This is an example of a functional test case.
24 | // Use XCTAssert and related functions to verify your tests produce the correct results.
25 | }
26 |
27 | func testPerformanceExample() {
28 | // This is an example of a performance test case.
29 | self.measure {
30 | // Put the code you want to measure the time of here.
31 | }
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/Common/Sources/Models/NumberFact/NumberFact+CoreDataProperties.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import CoreData
3 |
4 |
5 | extension NumberFact {
6 | @NSManaged public var number: Int64
7 | @NSManaged public var categoryValue: String
8 | @NSManaged public var text: String
9 | @NSManaged public var translatedText: String?
10 | @NSManaged public var currentLanguageValue: String
11 | @NSManaged public var translationLanguageValue: String
12 | @NSManaged public var isFavorite: Bool
13 |
14 |
15 | public var category: NumberFact.Category {
16 | get { NumberFact.Category(rawValue: categoryValue)! }
17 | set { categoryValue = newValue.rawValue }
18 | }
19 |
20 |
21 | public var currentLanguage: Language {
22 | get { Language(rawValue: currentLanguageValue)! }
23 | set { currentLanguageValue = newValue.rawValue }
24 | }
25 |
26 |
27 | public var translationLanguage: Language {
28 | get { Language(rawValue: translationLanguageValue)! }
29 | set { translationLanguageValue = newValue.rawValue }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/15-19-bitcoin-average-api-app/BitcoinAverageAPIFetcher/BitcoinAverageAPIFetcher/Scenes/Settings/SettingsView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SettingsView.swift
3 | // BitcoinAverageAPIFetcher
4 | //
5 | // Created by CypherPoet on 11/10/19.
6 | // ✌️
7 | //
8 |
9 | import SwiftUI
10 |
11 |
12 | struct SettingsView: View {
13 | @EnvironmentObject var viewModel: SettingsViewModel
14 | }
15 |
16 |
17 | // MARK: - Body
18 | extension SettingsView {
19 |
20 | var body: some View {
21 | List {
22 | Section(header: Text("Filtered Shitcoins")) {
23 | ForEach(viewModel.filteredShitcoins) { shitcoin in
24 | Text(shitcoin.name)
25 | }
26 | }
27 | }
28 | .navigationBarTitle("Settings", displayMode: .large)
29 | }
30 | }
31 |
32 |
33 | // MARK: - Computeds
34 | extension SettingsView {
35 |
36 |
37 | }
38 |
39 |
40 | // MARK: - View Variables
41 | extension SettingsView {
42 |
43 |
44 | }
45 |
46 |
47 |
48 | // MARK: - Preview
49 | struct SettingsView_Previews: PreviewProvider {
50 |
51 | static var previews: some View {
52 | SettingsView()
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/NumberFacts.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "CypherPoetCoreDataKit",
6 | "repositoryURL": "https://github.com/CypherPoet/CypherPoetCoreDataKit.git",
7 | "state": {
8 | "branch": null,
9 | "revision": "19e26a32e1cde119c41ceb50a59159fa94c5e1ae",
10 | "version": "0.0.11"
11 | }
12 | },
13 | {
14 | "package": "CypherPoetNetStack",
15 | "repositoryURL": "https://github.com/CypherPoet/CypherPoetNetStack.git",
16 | "state": {
17 | "branch": null,
18 | "revision": "b5dbd717cd51726ccba1041ee5981680aaeff95f",
19 | "version": "0.0.28"
20 | }
21 | },
22 | {
23 | "package": "CypherPoetSwiftUIKit",
24 | "repositoryURL": "https://github.com/CypherPoet/CypherPoetSwiftUIKit.git",
25 | "state": {
26 | "branch": null,
27 | "revision": "ae3c4bf2c35bd329ba0b6c185ad3938d92b41bfe",
28 | "version": "0.0.41"
29 | }
30 | }
31 | ]
32 | },
33 | "version": 1
34 | }
35 |
--------------------------------------------------------------------------------
/chapters-2-5/Playgrounds/Chapters 2-5.playground/Pages/03 - Mapping Keypaths.xcplaygroundpage/Sources/Coordinate.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import UIKit
3 |
4 |
5 | public struct Coordinate {
6 | public var x: CGFloat
7 | public var y: CGFloat
8 |
9 |
10 | public init(x: CGFloat = 0, y: CGFloat = 0) {
11 | self.x = x
12 | self.y = y
13 | }
14 | }
15 |
16 |
17 | extension Coordinate {
18 |
19 | public var quadrant: String {
20 | Self.quadrantDescriptionOf(x: x, y: y)
21 | }
22 |
23 |
24 | public static func quadrantDescriptionOf(x: CGFloat, y: CGFloat) -> String {
25 | switch (x, y) {
26 | case (0, 0):
27 | return "on the Origin"
28 | case let (x, _) where x == 0:
29 | return "on the Y-Axis"
30 | case let (_, y) where y == 0:
31 | return "on the X-Axis"
32 | case let (x, y) where x > 0:
33 | return "in Quadrant \(y > 0 ? "1" : "4")"
34 | case let (x, y) where x < 0:
35 | return "in Quadrant \(y > 0 ? "3" : "3")"
36 | default:
37 | fatalError()
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/chapters-2-5/Playgrounds/Chapters 2-5.playground/Pages/05 - Merge.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | //: [Previous](@previous)
2 |
3 | import Foundation
4 |
5 | import Combine
6 |
7 |
8 | var subscriptions = Set()
9 |
10 | demo(describing: "The `merge` publisher") {
11 | let numberStream1 = PassthroughSubject()
12 | let numberStream2 = PassthroughSubject()
13 |
14 |
15 | numberStream1
16 | .merge(with: numberStream2)
17 | .sink(
18 | receiveCompletion: { completion in
19 | print("(sink) Received Completion: \(completion)")
20 | },
21 | receiveValue: { value in
22 | print("(sink) Received Value: \(value)")
23 | }
24 | )
25 | .store(in: &subscriptions)
26 |
27 |
28 | numberStream1.send(111)
29 | numberStream1.send(111)
30 | numberStream2.send(222)
31 | numberStream2.send(222)
32 | numberStream1.send(111)
33 | numberStream2.send(222)
34 |
35 | numberStream1.send(completion: .finished)
36 | numberStream2.send(completion: .finished)
37 | }
38 |
39 |
40 | //: [Next](@next)
41 |
--------------------------------------------------------------------------------
/15-19-bitcoin-average-api-app/BitcoinAverageAPIFetcher/BitcoinAverageAPIFetcher/Reusables/Views/TimestampBadge.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TimestampBadge.swift
3 | // BitcoinAverageAPIFetcher
4 | //
5 | // Created by CypherPoet on 11/11/19.
6 | // ✌️
7 | //
8 |
9 | import SwiftUI
10 |
11 |
12 | struct TimestampBadge: View {
13 | let timeValue: Date
14 | }
15 |
16 |
17 | // MARK: - Body
18 | extension TimestampBadge {
19 |
20 | var body: some View {
21 | Text("\(timeValue, formatter: DateFormatters.priceReadingTimeBadge)")
22 | .font(.headline)
23 | .fontWeight(.heavy)
24 | .padding(10)
25 | .foregroundColor(.white)
26 | .background(Color.orange)
27 | .frame(idealWidth: 100)
28 | .cornerRadius(8)
29 | }
30 | }
31 |
32 |
33 | // MARK: - Computeds
34 | extension TimestampBadge {
35 |
36 |
37 | }
38 |
39 |
40 | // MARK: - View Variables
41 | extension TimestampBadge {
42 |
43 |
44 | }
45 |
46 |
47 |
48 | // MARK: - Preview
49 | struct TimestampBadge_Previews: PreviewProvider {
50 |
51 | static var previews: some View {
52 | TimestampBadge(timeValue: Date())
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/15-19-bitcoin-average-api-app/BitcoinAverageAPIFetcher/BitcoinAverageAPIFetcher.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "CypherPoetNetStack",
6 | "repositoryURL": "https://github.com/CypherPoet/CypherPoetNetStack.git",
7 | "state": {
8 | "branch": null,
9 | "revision": "5c5a1b4bd2e8ace3cada1a4d0358a998e66138a4",
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": "1a2b0e98984a36f5a480cafd815c386e2f777b1f",
19 | "version": "0.0.22"
20 | }
21 | },
22 | {
23 | "package": "SatoshiVSKit",
24 | "repositoryURL": "https://github.com/CypherPoet/SatoshiVSKit.git",
25 | "state": {
26 | "branch": null,
27 | "revision": "45a5ab600f192a20c22b3bafe482bd07d919932b",
28 | "version": "0.0.22"
29 | }
30 | }
31 | ]
32 | },
33 | "version": 1
34 | }
35 |
--------------------------------------------------------------------------------
/chapters-2-5/Playgrounds/Chapters 2-5.playground/Pages/03 - TryMap.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | //: [Previous](@previous)
2 |
3 | //: Several operators, including map, have a counterpart try operator that
4 | //: will take a closure that can throw an error.
5 | //:
6 | //: If you throw an error, it will emit that error downstream.
7 |
8 | import Foundation
9 | import Combine
10 |
11 |
12 | var subscriptions = Set()
13 |
14 |
15 | demo(describing: "tryMap(_:)") {
16 | Just("Directory name that does not exist")
17 | .tryMap { try FileManager.default.contentsOfDirectory(atPath: $0) }
18 | .sink(
19 | receiveCompletion: { completion in
20 | switch completion {
21 | case .failure(let error):
22 | print("(sink) Completed with error: \(error.localizedDescription)")
23 | case .finished:
24 | print("(sink) Successful completion")
25 | }
26 | },
27 | receiveValue: { value in
28 | print("(sink) Received value: \(value)")
29 | }
30 | )
31 | .store(in: &subscriptions)
32 | }
33 |
34 | //: [Next](@next)
35 |
--------------------------------------------------------------------------------
/chapters-2-5/Playgrounds/Chapters 2-5.playground/Pages/02 - Challenge.xcplaygroundpage/Sources/Rank.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public enum Rank: Int, CaseIterable {
4 | case two = 2
5 | case three
6 | case four
7 | case five
8 | case six
9 | case seven
10 | case eight
11 | case nine
12 | case ten
13 |
14 | case jack
15 | case queen
16 | case king
17 | case ace
18 | }
19 |
20 |
21 |
22 | // MARK: - Comparable
23 | extension Rank: Comparable {
24 | public static func < (lhs: Rank, rhs: Rank) -> Bool {
25 | switch (lhs, rhs) {
26 | case (_, _) where lhs == rhs:
27 | return false
28 | case (.ace, _):
29 | return false
30 | case (_, _):
31 | return lhs.rawValue < rhs.rawValue
32 | }
33 | }
34 | }
35 |
36 |
37 |
38 | // MARK: - CustomStringConvertible
39 | extension Rank: CustomStringConvertible {
40 | public var description: String {
41 | switch self {
42 | case .ace: return "A"
43 | case .jack: return "J"
44 | case .queen: return "Q"
45 | case .king: return "K"
46 | default:
47 | return "\(rawValue)"
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/chapters-2-5/Playgrounds/Chapters 2-5.playground/Pages/05 - zip.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | //: [Previous](@previous)
2 |
3 | import Foundation
4 | import Combine
5 |
6 |
7 | var subscriptions = Set()
8 |
9 |
10 | demo(describing: "The `zip` operator") {
11 | let numberStream = PassthroughSubject()
12 | let stringStream = PassthroughSubject()
13 |
14 | numberStream
15 | .zip(stringStream)
16 | .sink(
17 | receiveCompletion: { (completion) in
18 | print("(sink) Received Completion: \(completion)")
19 | },
20 | receiveValue: { (number, string) in
21 | print("(sink) Receive Value -- number: \(number)")
22 | print("(sink) Receive Value -- string: \(string)")
23 | }
24 | )
25 | .store(in: &subscriptions)
26 |
27 |
28 | [1, 2, 3].forEach { numberStream.send($0) }
29 |
30 |
31 | stringStream.send("⚡️")
32 | stringStream.send("🦄")
33 | stringStream.send("🤐")
34 | stringStream.send("🍁")
35 |
36 |
37 | numberStream.send(completion: .finished)
38 | stringStream.send(completion: .finished)
39 | }
40 |
41 | //: [Next](@next)
42 |
--------------------------------------------------------------------------------
/06-time-based-operators/Playgrounds/Chapter 6.playground/Sources/Data.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import Combine
3 |
4 |
5 | /// Sample data we use to feed a subject, simulating a user typing "Hello World"
6 | public let typingHelloWorld: [(TimeInterval, String)] = [
7 | (0.0, "H"),
8 | (0.1, "He"),
9 | (0.2, "Hel"),
10 | (0.3, "Hell"),
11 | (0.5, "Hello"),
12 | (0.6, "Hello "),
13 | (2.0, "Hello W"),
14 | (2.1, "Hello Wo"),
15 | (2.2, "Hello Wor"),
16 | (2.4, "Hello Worl"),
17 | (2.5, "Hello World")
18 | ]
19 |
20 |
21 | public extension Subject where Output == String {
22 |
23 | /// A function that can feed delayed values to a subject for testing and simulation purposes
24 | func feed(with data: [(TimeInterval, String)]) {
25 | var lastDelay: TimeInterval = 0
26 |
27 | for (delay, text) in data {
28 | lastDelay = delay
29 |
30 | DispatchQueue.main.asyncAfter(deadline: .now() + lastDelay) { [unowned self] in
31 | self.send(text)
32 | }
33 | }
34 |
35 | DispatchQueue.main.asyncAfter(deadline: .now() + lastDelay + 1.5) { [unowned self] in
36 | self.send(completion: .finished)
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/NumbersAPIService/Sources/Extensions/Endpoint+NumbersAPI.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import CypherPoetNetStack_Core
3 |
4 |
5 | extension Endpoint {
6 |
7 | public enum NumbersAPI {
8 | private static let host = "numbersapi.com"
9 |
10 |
11 | public static var randomYearFact: Endpoint {
12 | .init(
13 | scheme: "http",
14 | host: host,
15 | path: "/random/year"
16 | )
17 | }
18 |
19 |
20 | public static var randomDateFact: Endpoint {
21 | .init(
22 | scheme: "http",
23 | host: host,
24 | path: "/random/date"
25 | )
26 | }
27 |
28 |
29 | public static var randomTriviaFact: Endpoint {
30 | .init(
31 | scheme: "http",
32 | host: host,
33 | path: "/random/trivia"
34 | )
35 | }
36 |
37 |
38 | public static var randomMathFact: Endpoint {
39 | .init(
40 | scheme: "http",
41 | host: host,
42 | path: "/random/math"
43 | )
44 | }
45 | }
46 | }
47 |
48 |
49 |
--------------------------------------------------------------------------------
/17-schedulers/Playgrounds/MyPlayground.playground/Pages/Challenge 1 - Stop the Timer.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | //: [Previous](@previous)
2 |
3 | import Foundation
4 | import Combine
5 | import PlaygroundSupport
6 |
7 | //: ## Challenge 1: Stop the Timer
8 | //:
9 | //: In this chapter’s section about DispatchQueue you created a cancellable
10 | //: timer to feed your source publisher with values.
11 | //:
12 | //: Devise two different ways of stopping the timer after 4 seconds.
13 |
14 |
15 | let incrementer = Timer
16 | .publish(every: 1.0, on: .main, in: .common)
17 | .autoconnect()
18 | .scan(0) { (accumulatedCount, _) in
19 | accumulatedCount + 1
20 | }
21 |
22 | let eventQueue = DispatchQueue(label: "Custom Serial Queue", qos: .userInitiated)
23 |
24 |
25 |
26 | let numberPublisher = incrementer
27 | .receive(on: eventQueue)
28 | .eraseToAnyPublisher()
29 |
30 |
31 | let subscription = numberPublisher.sink(receiveValue: { print($0) })
32 |
33 |
34 | // MARK: - Solution 1
35 | //eventQueue.schedule(
36 | // after: eventQueue.now.advanced(by: .seconds(4)),
37 | // tolerance: .seconds(0.1)
38 | //) {
39 | // subscription.cancel()
40 | //}
41 |
42 |
43 | // MARK: - Solution 2
44 |
45 | eventQueue.asyncAfter(deadline: .now() + 4) {
46 | subscription.cancel()
47 | }
48 |
49 | //: [Next](@next)
50 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/TranslationService/Sources/Extensions/Endpoint+TranslationAPI.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import Common
3 | import CypherPoetNetStack_Core
4 |
5 |
6 | extension Endpoint {
7 |
8 | public enum TranslationAPI {
9 | private static let apiKey = "trnsl.1.1.20190822T112140Z.d96fa7f4ed58ada0.f7a7297172fb385a6ae2c415b252b0d530e6f495"
10 |
11 | private static let scheme = "https"
12 | private static let host = "translate.yandex.net"
13 | private static let path = "/api/v1.5/tr.json/translate"
14 |
15 |
16 | public static func translation(
17 | for text: String,
18 | convertingFrom sourceLanguage: Language,
19 | to targetLanguage: Language
20 | ) -> Endpoint {
21 | .init(
22 | scheme: scheme,
23 | host: host,
24 | path: path,
25 | queryItems: [
26 | URLQueryItem(name: "key", value: apiKey),
27 | URLQueryItem(name: "text", value: text),
28 | URLQueryItem(
29 | name: "lang",
30 | value: "\(sourceLanguage.code)-\(targetLanguage.code)"
31 | ),
32 | ]
33 | )
34 | }
35 | }
36 | }
37 |
38 |
39 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/Common/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: "Common",
8 | platforms: [
9 | .iOS(.v13),
10 | .macOS(.v10_15),
11 | ],
12 | products: [
13 | // Products define the executables and libraries produced by a package, and make them visible to other packages.
14 | .library(
15 | name: "Common",
16 | targets: [
17 | "Common",
18 | ]
19 | ),
20 | ],
21 | dependencies: [
22 | // Dependencies declare other packages that this package depends on.
23 | // .package(url: /* package url */, from: "1.0.0"),
24 | .package(url: "https://github.com/CypherPoet/CypherPoetCoreDataKit.git", from: "0.0.11"),
25 | ],
26 | targets: [
27 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
28 | // Targets can depend on other targets in this package, and on products in packages which this package depends on.
29 | .target(
30 | name: "Common",
31 | dependencies: [
32 | "CypherPoetCoreDataKit"
33 | ],
34 | path: "Sources/"
35 | ),
36 | ]
37 | )
38 |
--------------------------------------------------------------------------------
/19-testing-combine-code/Projects/ColorCalc/ColorCalcTests/ Combine Operators/CombineOperatorsTests+ShareReplay.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CombineOperatorsTests+ShareReplay.swift
3 | // ColorCalc
4 | //
5 | // Created by CypherPoet on 12/21/19.
6 | // ✌️
7 | //
8 |
9 | import XCTest
10 | import Combine
11 | import CypherPoetCombineKit_ShareReplay
12 |
13 |
14 | extension CombineOperatorsTests {
15 |
16 | func testShareReplay() {
17 | let basePublisher = PassthroughSubject()
18 | let numberStream = basePublisher.shareReplay(capacity: 2)
19 |
20 | let expectation = XCTestExpectation(description: "Publisher should complete successfully.")
21 | let expectedValues = [1, 2, 3, 4, 3, 4, 5, 5]
22 | var receivedValues: [Int] = []
23 |
24 |
25 | numberStream
26 | .sink(receiveValue: { receivedValues.append($0) })
27 | .store(in: &subscriptions)
28 |
29 |
30 | basePublisher.send(1)
31 | basePublisher.send(2)
32 | basePublisher.send(3)
33 | basePublisher.send(4)
34 |
35 |
36 | numberStream
37 | .sink(receiveValue: { receivedValues.append($0) })
38 | .store(in: &subscriptions)
39 |
40 |
41 | basePublisher.send(5)
42 |
43 | XCTAssertEqual(receivedValues, expectedValues)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/06-time-based-operators/Playgrounds/Chapter 6.playground/Pages/Shifting Time.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | //: [Previous](@previous)
2 |
3 | import Combine
4 | import SwiftUI
5 | import PlaygroundSupport
6 |
7 | //: Creates a publisher that emits one value every second,
8 | //: then delays it by 1.5 seconds and displays both timelines simultaneously
9 | //: to compare them.
10 |
11 |
12 | let valuesPerSecond = 1.0
13 | let delayInSeconds = 1.5
14 |
15 | let basePublisher = PassthroughSubject()
16 |
17 | let delayedPublisher = basePublisher
18 | .delay(for: .seconds(delayInSeconds), scheduler: DispatchQueue.main)
19 |
20 |
21 | let subscriber = Timer
22 | .publish(every: 1.0 / valuesPerSecond, on: .main, in: .common)
23 | .autoconnect()
24 | .subscribe(basePublisher)
25 |
26 |
27 | let baseTimeline = TimelineView(
28 | title: "Emitted Values (\(valuesPerSecond) per second)",
29 | events: []
30 | )
31 |
32 |
33 | let delayedTimeline = TimelineView(
34 | title: "Delayed Values (delayed by \(delayInSeconds) seconds)",
35 | events: []
36 | )
37 |
38 |
39 | let view = VStack(spacing: 50) {
40 | baseTimeline
41 | delayedTimeline
42 | }
43 |
44 |
45 | PlaygroundPage.current.liveView = UIHostingController(rootView: view)
46 |
47 |
48 | basePublisher.displayEvents(in: baseTimeline)
49 | delayedPublisher.displayEvents(in: delayedTimeline)
50 |
51 |
52 | //: [Next](@next)
53 |
54 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Models/ChuckNorrisJokes.xcdatamodeld/ChuckNorrisJokes.xcdatamodel/contents:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Models/ChuckNorrisJokes.xcdatamodeld/ChuckNorrisJokes.xcdatamodel/contents:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/17-schedulers/Playgrounds/MyPlayground.playground/Pages/ImmediateScheduler.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | //: [Previous](@previous)
2 |
3 | //: ## ImmediateScheduler
4 |
5 |
6 | import Foundation
7 | import Combine
8 | import PlaygroundSupport
9 | import SwiftUI
10 |
11 |
12 | var subscriptions = Set()
13 |
14 | let computationPublisher = Publishers.ExpensiveComputation(duration: 3)
15 | let customQueue = DispatchQueue(label: "Serial queue")
16 |
17 | let startingThreadNumber = Thread.current.number
18 |
19 |
20 | let incrementer = Timer
21 | .publish(every: 1.0, on: .main, in: .common)
22 | .autoconnect()
23 | .scan(0) { counter, _ in
24 | counter + 1
25 | }
26 |
27 |
28 | //: The `ImmediateScheduler` “schedules” immediately on the current thread.
29 |
30 | demo(describing: "ImmediateScheduler") {
31 | let setupPublisher = { recorder in
32 | incrementer
33 | .receive(on: DispatchQueue.global())
34 | .recordThread(using: recorder)
35 | .receive(on: ImmediateScheduler.shared)
36 | // .receive(on: customQueue)
37 | .recordThread(using: recorder)
38 | .eraseToAnyPublisher()
39 | }
40 |
41 | let view = ThreadRecorderView(title: "Using ImmediateScheduler", setup: setupPublisher)
42 |
43 | PlaygroundPage.current.liveView = UIHostingController(rootView: view)
44 | }
45 |
46 | //: [Next](@next)
47 |
--------------------------------------------------------------------------------
/chapters-2-5/Playgrounds/Chapters 2-5.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Models/ChuckNorrisJokes.xcdatamodeld/ChuckNorrisJokes.xcdatamodel/contents:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Models/ChuckNorrisJokes.xcdatamodeld/ChuckNorrisJokes.xcdatamodel/contents:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/chapters-2-5/Playgrounds/Chapters 2-5.playground/Pages/03 - Replacing Upstream Output.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | //: [Previous](@previous)
2 |
3 | import Foundation
4 | import Combine
5 |
6 |
7 | var subscriptions = Set()
8 |
9 |
10 | demo(describing: "`replaceNil`") {
11 | ["⚡️", "⚡️", nil]
12 | .publisher
13 | .replaceNil(with: "💥")
14 | .sink { print($0) }
15 | .store(in: &subscriptions)
16 | }
17 |
18 |
19 | demo(describing: "`replaceNil` and unwrap") {
20 | ["⚡️", "⚡️", nil]
21 | .publisher
22 | .replaceNil(with: "💥")
23 | .map({ $0! })
24 | .sink { print($0) }
25 | .store(in: &subscriptions)
26 | }
27 |
28 |
29 | demo(describing: "`replaceEmpty`") {
30 | [].publisher
31 | .replaceEmpty(with: "🦕")
32 | .sink(
33 | receiveCompletion: { completion in
34 | print(completion)
35 | },
36 | receiveValue: { value in
37 | print(value)
38 | }
39 | )
40 | .store(in: &subscriptions)
41 |
42 |
43 | let empty = Empty()
44 |
45 | empty
46 | .replaceEmpty(with: 42)
47 | .sink(
48 | receiveCompletion: { completion in print(completion) },
49 | receiveValue: { value in print(value) }
50 | )
51 | .store(in: &subscriptions)
52 | }
53 |
54 | //: [Next](@next)
55 |
--------------------------------------------------------------------------------
/13-resource-management/Playgrounds/MyPlayground.playground/Pages/The Share Operator.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | //: [Previous](@previous)
2 |
3 | import Foundation
4 | import Combine
5 |
6 |
7 | var subscriptions = Set()
8 |
9 | let url = URL(string: "https://developer.apple.com/design/human-interface-guidelines")!
10 |
11 |
12 | demo(describing: "The `share` operator") {
13 |
14 | let sharedStream = URLSession.shared
15 | .dataTaskPublisher(for: url)
16 | .map(\.data)
17 | .print("Shared publisher")
18 | .share()
19 |
20 |
21 | print("Subscribing first...")
22 |
23 | sharedStream
24 | .sink(
25 | receiveCompletion: { completion in
26 | print("(subscription 1) - Completion")
27 | },
28 | receiveValue: { data in
29 | print("(subscription 1) Received data: \(data)")
30 | }
31 | )
32 | .store(in: &subscriptions)
33 |
34 |
35 |
36 | print("Subscribing second...")
37 |
38 | sharedStream
39 | .sink(
40 | receiveCompletion: { completion in
41 | print("(subscription 2) - Completion")
42 | },
43 | receiveValue: { data in
44 | print("(subscription 2) Received data: \(data)")
45 | }
46 | )
47 | .store(in: &subscriptions)
48 |
49 | }
50 |
51 | //: [Next](@next)
52 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/Common/Sources/Models/Language/Language.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 |
4 | public enum Language: String {
5 | case english
6 | case french
7 | case spanish
8 | }
9 |
10 |
11 | extension Language: CaseIterable {}
12 |
13 | extension Language: Identifiable {
14 | public var id: String { code }
15 | }
16 |
17 |
18 |
19 | extension Language {
20 |
21 | public init?(code: String) {
22 | guard let language = Self.allCases.first(where: { $0.code == code }) else {
23 | return nil
24 | }
25 |
26 | self = language
27 | }
28 |
29 |
30 | public var shortName: String {
31 | switch self {
32 | case .english:
33 | return "EN"
34 | case .spanish:
35 | return "ES"
36 | case .french:
37 | return "FR"
38 | }
39 | }
40 |
41 |
42 | public var longName: String {
43 | switch self {
44 | case .english:
45 | return "English"
46 | case .spanish:
47 | return "Spanish"
48 | case .french:
49 | return "French"
50 | }
51 | }
52 |
53 |
54 | public var code: String {
55 | switch self {
56 | case .english:
57 | return "en"
58 | case .spanish:
59 | return "es"
60 | case .french:
61 | return "fr"
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/chapters-2-5/Playgrounds/Chapters 2-5.playground/Pages/04- Challenge.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | //: [Previous](@previous)
2 |
3 | //: 🥅 Challenge: Filter all the things
4 | //:
5 | //: Create an example that publishes a collection of numbers from 1 through 100,
6 | //: and use filtering operators to:
7 | //:
8 | //: - Skip the first 50 values emitted by the upstream publisher.
9 | //: - Take the next 20 values after those first 50 values.
10 | //: - Only take even numbers.
11 | //:
12 | //: The output of your example should produce the following numbers, one per line:
13 | //:
14 | //: 52 54 56 58 60 62 64 66 68 70
15 | //:
16 |
17 |
18 | import Foundation
19 | import Combine
20 |
21 |
22 | var subscriptions = Set()
23 |
24 |
25 | demo(describing: "Challenge for Chapter 4") {
26 | let numberPublisher = PassthroughSubject()
27 |
28 | numberPublisher
29 | .dropFirst(50)
30 | .prefix(20)
31 | .filter({ $0.isMultiple(of: 2) })
32 | .sink(
33 | receiveCompletion: { completion in
34 | print("(sink) Received completion: \(completion)")
35 | },
36 | receiveValue: { value in
37 | print("(sink) Received value: \(value)")
38 | }
39 | )
40 | .store(in: &subscriptions)
41 |
42 |
43 | for number in (1...100) {
44 | numberPublisher.send(number)
45 | }
46 | }
47 |
48 | //: [Next](@next)
49 |
--------------------------------------------------------------------------------
/chapters-2-5/Playgrounds/Chapters 2-5.playground/Pages/03 - Challenge.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | //: [Previous](@previous)
2 |
3 | //: 🥅 Challenge: Create a phone number lookup using transforming operators
4 |
5 | import Foundation
6 | import Combine
7 |
8 |
9 | var subscribers = Set()
10 |
11 |
12 | demo(
13 | describing: "Challenge: Creating a phone number lookup using transforming operators"
14 | ) {
15 | let inputReceiver = PassthroughSubject()
16 |
17 | inputReceiver
18 | .map(Phone.numberFromInput)
19 | .replaceNil(with: 0)
20 | .collect(10)
21 | .map { digits in
22 | digits.reduce("", { (digitString, currentDigit) in
23 | "\(digitString)\(currentDigit)"
24 | })
25 | }
26 | .map(PhoneBook.formattedPhoneNumber(from:))
27 | .print()
28 | .map(PhoneBook.dial(phoneNumber:))
29 | .sink(
30 | receiveCompletion: { completion in
31 | print("(sink) Received completion: \(completion)")
32 | },
33 | receiveValue: { value in
34 | print("(sink) Received value: \(value)")
35 | }
36 | )
37 | .store(in: &subscribers)
38 |
39 |
40 | "0123456789".forEach { inputReceiver.send($0) }
41 | "7777777777".forEach { inputReceiver.send($0) }
42 | "✌️🙂🍁🦅3🙂🍁🦅✌️🙂".forEach { inputReceiver.send($0) }
43 | }
44 |
45 |
46 |
47 | //: [Next](@next)
48 |
--------------------------------------------------------------------------------
/chapters-2-5/Playgrounds/Chapters 2-5.playground/Pages/05- combineLatest.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | //: [Previous](@previous)
2 |
3 | import Foundation
4 | import Combine
5 |
6 | //: `combineLatest` is another operator that lets you combine different publishers.
7 | //: It also lets you combine publishers of different
8 | //: value types, which can be extremely useful.
9 |
10 |
11 | var subscriptions = Set()
12 |
13 |
14 | demo(describing: "The `combineLatest` operator") {
15 | let numberStream = PassthroughSubject()
16 | let stringStream = PassthroughSubject()
17 |
18 | numberStream
19 | .combineLatest(stringStream)
20 | .sink(receiveCompletion: { (completion) in
21 | print("(sink) Received Completion: \(completion)")
22 | }, receiveValue: { (number, string) in
23 | print("(sink) Receive Value -- number: \(number)")
24 | print("(sink) Receive Value -- string: \(string)")
25 | })
26 | .store(in: &subscriptions)
27 |
28 |
29 | numberStream.send(1)
30 | numberStream.send(2)
31 | numberStream.send(3)
32 |
33 | stringStream.send("Foo")
34 | stringStream.send("Bar")
35 | stringStream.send("Baz")
36 | stringStream.send("Wha?")
37 | numberStream.send(7)
38 | stringStream.send("Whoa!")
39 |
40 | numberStream.send(completion: .finished)
41 | stringStream.send(completion: .finished)
42 | }
43 |
44 |
45 |
46 | //: [Next](@next)
47 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/NumberFacts/NumberFacts/Data/NumberFacts.xcdatamodeld/NumberFacts.xcdatamodel/contents:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/15-19-bitcoin-average-api-app/BitcoinAverageAPIFetcher/BitcoinAverageAPIFetcher/Data/State/SettingsState.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SettingsState.swift
3 | // BitcoinAverageAPIFetcher
4 | //
5 | // Created by CypherPoet on 11/9/19.
6 | // ✌️
7 | //
8 |
9 | import SwiftUI
10 | import Combine
11 | import CypherPoetSwiftUIKit
12 | import SatoshiVSKit
13 |
14 |
15 | struct SettingsState {
16 | var filteredShitcoins: [Shitcoin] = []
17 | }
18 |
19 |
20 | //enum SettingsSideEffect: SideEffect {
21 | // case add(shitcoinToFilters: Shitcoin)
22 | // case remove(shitcoinFromFilters: Shitcoin)
23 |
24 | //
25 | // func mapToAction() -> AnyPublisher {
26 | // switch self {
27 | // case .add(let shitcoinToFilters):
28 | // case .remove(let shitcoinFromFilters):
29 | // }
30 | // }
31 | //}
32 |
33 |
34 |
35 | enum SettingsAction {
36 | case add(shitcoinToFilters: Shitcoin)
37 | case remove(shitcoinFromFilters: Shitcoin)
38 | }
39 |
40 |
41 |
42 | // MARK: - Reducer
43 | let settingsReducer = Reducer { state, action in
44 | switch action {
45 | case .add(let shitcoin):
46 | var filteredShitcoins = state.filteredShitcoins
47 | filteredShitcoins.append(shitcoin)
48 |
49 | state.filteredShitcoins = filteredShitcoins.sorted()
50 | case .remove(let shitcoin):
51 | if let index = state.filteredShitcoins.firstIndex(of: shitcoin) {
52 | state.filteredShitcoins.remove(at: index)
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/NumberFacts/NumberFacts/Scenes/HomeView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HomeView.swift
3 | // NumberFacts
4 | //
5 | // Created by CypherPoet on 2/19/20.
6 | // ✌️
7 | //
8 |
9 | import SwiftUI
10 |
11 |
12 | struct HomeView {
13 | enum Tab {
14 | case numberFactsFeed
15 | case favoriteNumberFacts
16 | }
17 |
18 |
19 | @State private var activeTab: Tab = .numberFactsFeed
20 | }
21 |
22 |
23 | // MARK: - View
24 | extension HomeView: View {
25 |
26 | var body: some View {
27 | TabView(selection: $activeTab) {
28 | NumberFactsFeedContainerView()
29 | .tabItem {
30 | Image(systemName: "number.circle.fill")
31 | Text("Feed")
32 | }
33 | .tag(Tab.numberFactsFeed)
34 |
35 |
36 | FavoriteNumberFactsContainerView()
37 | .tabItem {
38 | Image(systemName: "star.fill")
39 | Text("Favorites")
40 | }
41 | .tag(Tab.favoriteNumberFacts)
42 | }
43 | }
44 | }
45 |
46 |
47 | // MARK: - Computeds
48 | extension HomeView {
49 | }
50 |
51 |
52 | // MARK: - View Variables
53 | extension HomeView {
54 | }
55 |
56 |
57 | // MARK: - Private Helpers
58 | private extension HomeView {
59 | }
60 |
61 |
62 |
63 | // MARK: - Preview
64 | struct HomeView_Previews: PreviewProvider {
65 |
66 | static var previews: some View {
67 | HomeView()
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/chapters-2-5/Playgrounds/Chapters 2-5.playground/Pages/04 - Dropping Values.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | //: [Previous](@previous)
2 |
3 |
4 | import Foundation
5 | import Combine
6 |
7 | var subscriptions = Set()
8 |
9 |
10 | demo(describing: "The `dropFirst` operator") {
11 | let numbers = (1...10)
12 |
13 | numbers
14 | .publisher
15 | .dropFirst(4)
16 | .sink(receiveValue: { print($0) })
17 | .store(in: &subscriptions)
18 | }
19 |
20 |
21 | demo(describing: "The `drop(while:)` operator") {
22 | let numbers = (1...10)
23 |
24 | numbers
25 | .publisher
26 | .drop(while: { number in
27 | print("Evaluating drop(while:) predicate")
28 |
29 | return number % 4 != 0
30 | })
31 | .sink(receiveValue: { print($0) })
32 | .store(in: &subscriptions)
33 | }
34 |
35 |
36 | demo(describing: "The `drop(untilOutputFrom:)` operator") {
37 | let readyFlag = PassthroughSubject()
38 | let numberPublisher = PassthroughSubject()
39 |
40 | numberPublisher
41 | .drop(untilOutputFrom: readyFlag)
42 | .sink(receiveValue: { print($0) })
43 | .store(in: &subscriptions)
44 |
45 |
46 | numberPublisher.send(1)
47 | numberPublisher.send(1)
48 | numberPublisher.send(2)
49 | numberPublisher.send(3)
50 | numberPublisher.send(5)
51 |
52 | readyFlag.send()
53 |
54 | numberPublisher.send(8)
55 | }
56 |
57 |
58 | //: [Next](@next)
59 |
--------------------------------------------------------------------------------
/19-testing-combine-code/Projects/ColorCalc/ColorCalcTests/ Combine Operators/CombineOperatorsTests+Collect.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CombineOperatorsTests+Collect.swift
3 | // ColorCalc
4 | //
5 | // Created by CypherPoet on 12/20/19.
6 | // ✌️
7 | //
8 |
9 | import XCTest
10 | import Combine
11 |
12 |
13 | extension CombineOperatorsTests {
14 |
15 | /// The `collect` operator will buffer values emitted by an upstream publisher,
16 | /// wait for it to complete, and then emit an array containing those values downstream.
17 | func testCollect() {
18 | let basePublisher = PassthroughSubject()
19 | let fibs = [1, 1, 2, 3, 5, 8, 13, 21]
20 | let expectation = XCTestExpectation(description: "Publisher should complete successfully.")
21 |
22 |
23 | basePublisher
24 | .collect()
25 | .sink(
26 | receiveCompletion: { completion in
27 | switch completion {
28 | case .finished:
29 | expectation.fulfill()
30 | case .failure:
31 | XCTFail()
32 | }
33 | },
34 | receiveValue: { numbers in
35 | XCTAssertEqual(numbers, fibs)
36 | }
37 | )
38 | .store(in: &subscriptions)
39 |
40 | fibs.forEach { basePublisher.send($0) }
41 | basePublisher.send(completion: .finished)
42 |
43 | wait(for: [expectation], timeout: 2.0)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/15-19-bitcoin-average-api-app/BitcoinAverageAPIFetcher/BitcoinAverageAPIFetcher/Reusables/Views/ShitcoinFilterListItem.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ShitcoinFilterListItem.swift
3 | // BitcoinAverageAPIFetcher
4 | //
5 | // Created by CypherPoet on 11/16/19.
6 | // ✌️
7 | //
8 |
9 | import SwiftUI
10 | import SatoshiVSKit
11 |
12 |
13 | struct ShitcoinFilterListItem: View {
14 | let shitcoin: Shitcoin
15 | let isSelected: Bool
16 |
17 | let onSelectionToggled: ((Shitcoin, Bool) -> Void)
18 | }
19 |
20 |
21 | // MARK: - Body
22 | extension ShitcoinFilterListItem {
23 |
24 | var body: some View {
25 | Button(action: {
26 | self.onSelectionToggled(self.shitcoin, !self.isSelected)
27 | }) {
28 | HStack {
29 | Text(shitcoin.name)
30 |
31 | Spacer()
32 |
33 | if isSelected {
34 | Image(systemName: "checkmark")
35 | .imageScale(.large)
36 | .foregroundColor(.green)
37 | .transition(.opacity)
38 | }
39 | }
40 | }
41 | }
42 | }
43 |
44 |
45 | // MARK: - Computeds
46 | extension ShitcoinFilterListItem {
47 | }
48 |
49 |
50 | // MARK: - View Variables
51 | extension ShitcoinFilterListItem {
52 | }
53 |
54 |
55 |
56 | // MARK: - Preview
57 | struct ShitcoinFilterListItem_Previews: PreviewProvider {
58 |
59 | static var previews: some View {
60 | ShitcoinFilterListItem(shitcoin: .ada, isSelected: true) { (_, _) in }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/08-photo-collage-app/Collage/Collage/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Collage
4 | //
5 | // Created by Brian Sipple on 10/28/19.
6 | // Copyright © 2019 CypherPoet. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 |
15 |
16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
17 | // Override point for customization after application launch.
18 | return true
19 | }
20 |
21 | // MARK: UISceneSession Lifecycle
22 |
23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
24 | // Called when a new scene session is being created.
25 | // Use this method to select a configuration to create the new scene with.
26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
27 | }
28 |
29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
30 | // Called when the user discards a scene session.
31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
33 | }
34 |
35 |
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/chapters-2-5/Playgrounds/Chapters 2-5.playground/Pages/02 - Challenge.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | //: [Previous](@previous)
2 |
3 | /// Challenge: Create a Blackjack card dealer
4 |
5 | import Foundation
6 | import Combine
7 |
8 | demo(describing: "Challenge: Create a Blackjack card dealer") {
9 | var subscriptions = Set()
10 | var dealersDeck = Deck.deckOf52()
11 |
12 | let dealtHand = PassthroughSubject()
13 |
14 | func deal(_ cardCount: Int) {
15 | var cardsRemaining = dealersDeck.cards.count
16 | var hand = Hand()
17 |
18 | for _ in 0 ..< cardCount {
19 | let randomIndex = Int.random(in: 0 ..< cardsRemaining)
20 | hand.append(dealersDeck.cards[randomIndex])
21 |
22 | dealersDeck.cards.remove(at: randomIndex)
23 | cardsRemaining -= 1
24 |
25 | if hand.isBusted {
26 | dealtHand.send(completion: .failure(.busted))
27 | } else {
28 | dealtHand.send(hand)
29 | }
30 | }
31 | }
32 |
33 | dealtHand
34 | .sink(receiveCompletion: { completion in
35 | switch completion {
36 | case .failure(let error):
37 | print(error)
38 | default:
39 | print("Hand finished")
40 | }
41 | }) { hand in
42 | print("Current hand: \(hand.cardString), Points: \(hand.points)")
43 | }
44 | .store(in: &subscriptions)
45 |
46 |
47 | deal(3)
48 | }
49 |
50 | //: [Next](@next)
51 |
--------------------------------------------------------------------------------
/13-resource-management/Playgrounds/MyPlayground.playground/Pages/The Share Operator Pitfalls.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | //: [Previous](@previous)
2 |
3 | import Foundation
4 | import Combine
5 |
6 |
7 | var subscriptions = Set()
8 |
9 | let url = URL(string: "https://developer.apple.com/design/human-interface-guidelines")!
10 |
11 |
12 | demo(describing: "Pitfalls of the `share` operator: being too late with future subscriptions") {
13 |
14 | let sharedStream = URLSession.shared
15 | .dataTaskPublisher(for: url)
16 | .map(\.data)
17 | .print("Shared publisher")
18 | .share()
19 |
20 |
21 | print("Subscribing first...")
22 |
23 | sharedStream
24 | .sink(
25 | receiveCompletion: { completion in
26 | print("(subscription 1) - Completion")
27 | },
28 | receiveValue: { data in
29 | print("(subscription 1) Received data: \(data)")
30 | }
31 | )
32 | .store(in: &subscriptions)
33 |
34 |
35 | DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
36 | print("Subscribing second...")
37 |
38 | sharedStream
39 | .sink(
40 | receiveCompletion: { completion in
41 | print("(subscription 2) - Completion")
42 | },
43 | receiveValue: { data in
44 | print("(subscription 2) Received data: \(data)")
45 | }
46 | )
47 | .store(in: &subscriptions)
48 | }
49 | }
50 |
51 |
52 | //: [Next](@next)
53 |
--------------------------------------------------------------------------------
/06-time-based-operators/Playgrounds/Chapter 6.playground/Pages/Holding Off On Events - Timeout.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | //: [Previous](@previous)
2 |
3 | import Combine
4 | import SwiftUI
5 | import PlaygroundSupport
6 |
7 |
8 | let timeoutLimit: TimeInterval = 5.0
9 |
10 | enum TimeoutError: Error {
11 | case timedOut
12 | }
13 |
14 | // MARK: - Publishers
15 |
16 | let basePublisher = PassthroughSubject()
17 |
18 |
19 | // The timedOut subject publisher will time out after X seconds without the
20 | // upstream publisher emitting any value
21 | let timeoutPublisher = basePublisher
22 | .timeout(
23 | .seconds(timeoutLimit),
24 | scheduler: RunLoop.main,
25 | customError: { .timedOut }
26 | )
27 |
28 |
29 |
30 | // MARK: - Timelines
31 |
32 | let baseTimeline = TimelineView(
33 | title: "Button Taps",
34 | events: []
35 | )
36 |
37 |
38 |
39 |
40 | // MARK: - View Setup
41 |
42 | let view = VStack(spacing: 50) {
43 | baseTimeline
44 |
45 | Button(action: {
46 | // Send a signal to the base publisher -- which, in turn, will
47 | // be seen by the `timeoutPublisher`, which is a timeout-modfied version
48 | // of that publisher wired to stop if a signal isn't sent to the `basePublisher` within
49 | // the timeout period.
50 | basePublisher.send()
51 | }) {
52 | Text("Tap be within \(timeoutLimit) seconds")
53 | }
54 | }
55 |
56 |
57 | PlaygroundPage.current.liveView = UIHostingController(rootView: view)
58 |
59 |
60 | timeoutPublisher.displayEvents(in: baseTimeline)
61 |
62 |
63 | //: [Next](@next)
64 |
--------------------------------------------------------------------------------
/15-19-bitcoin-average-api-app/BitcoinAverageAPIFetcher/BitcoinAverageAPIFetcher/Scenes/Settings/SettingsContainerView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SettingsContainerView.swift
3 | // BitcoinAverageAPIFetcher
4 | //
5 | // Created by CypherPoet on 11/15/19.
6 | // ✌️
7 | //
8 |
9 | import SwiftUI
10 |
11 |
12 | struct SettingsContainerView: View {
13 | @EnvironmentObject private var store: AppStore
14 | @State private var isShowingFilterPicker = false
15 | }
16 |
17 |
18 | // MARK: - Body
19 | extension SettingsContainerView {
20 |
21 | var body: some View {
22 | NavigationView {
23 | SettingsView()
24 | .navigationBarItems(trailing: addFilterButton)
25 | .sheet(isPresented: $isShowingFilterPicker) {
26 | ShitcoinFilterSelectionView()
27 | .environmentObject(self.store)
28 | }
29 | }
30 | }
31 | }
32 |
33 |
34 | // MARK: - Computeds
35 | extension SettingsContainerView {
36 |
37 |
38 | }
39 |
40 |
41 | // MARK: - View Variables
42 | extension SettingsContainerView {
43 |
44 | private var addFilterButton: some View {
45 | Button(action: {
46 | self.isShowingFilterPicker = true
47 | }) {
48 | Text("Add Filter")
49 | }
50 | }
51 |
52 | }
53 |
54 |
55 |
56 | // MARK: - Preview
57 | struct SettingsContainerView_Previews: PreviewProvider {
58 |
59 | static var previews: some View {
60 | SettingsContainerView()
61 | .environmentObject(SampleStore.default)
62 | .environmentObject(SettingsViewModel(store: SampleStore.default))
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/15-19-bitcoin-average-api-app/BitcoinAverageAPIFetcher/BitcoinAverageAPIFetcher/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // BitcoinAverageAPIFetcher
4 | //
5 | // Created by Brian Sipple on 11/9/19.
6 | // Copyright © 2019 CypherPoet. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 |
15 |
16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
17 | // Override point for customization after application launch.
18 | return true
19 | }
20 |
21 | // MARK: UISceneSession Lifecycle
22 |
23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
24 | // Called when a new scene session is being created.
25 | // Use this method to select a configuration to create the new scene with.
26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
27 | }
28 |
29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
30 | // Called when the user discards a scene session.
31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
33 | }
34 |
35 |
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/NumberFacts/NumberFacts/Scenes/Favorite Number Facts/FavoriteNumberFactsListView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FavoriteNumberFactsListView.swift
3 | // NumberFacts
4 | //
5 | // Created by CypherPoet on 2/23/20.
6 | // ✌️
7 | //
8 |
9 | import SwiftUI
10 | import Common
11 |
12 |
13 | struct FavoriteNumberFactsListView {
14 | var numberFacts: [NumberFact]
15 |
16 | let onFactsDeleted: ((IndexSet) -> Void)?
17 | // let onLanguageToggled: ((Language) -> Void)?
18 | }
19 |
20 |
21 | // MARK: - View
22 | extension FavoriteNumberFactsListView: View {
23 |
24 | var body: some View {
25 | List {
26 | ForEach(numberFacts) { numberFact in
27 | Text(numberFact.text)
28 | }
29 | .onDelete(perform: onFactsDeleted)
30 | }
31 | .navigationBarTitle("Favorite Facts")
32 | }
33 | }
34 |
35 |
36 | // MARK: - Computeds
37 | extension FavoriteNumberFactsListView {
38 | }
39 |
40 |
41 | // MARK: - View Variables
42 | extension FavoriteNumberFactsListView {
43 | }
44 |
45 |
46 | // MARK: - Private Helpers
47 | private extension FavoriteNumberFactsListView {
48 | }
49 |
50 |
51 |
52 | // MARK: - Preview
53 | struct FavoriteNumberFactsListView_Previews: PreviewProvider {
54 |
55 | static var previews: some View {
56 | NavigationView {
57 | FavoriteNumberFactsListView(
58 | numberFacts: [
59 | PreviewData.NumberFacts.sample1,
60 | ],
61 | onFactsDeleted: { _ in }
62 | // onLanguageToggled: { _ in }
63 | )
64 | }
65 | }
66 | }
67 |
68 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Combine: Asynchronous Programming with Swift
2 |
3 | _Projects, playgrounds, and other materials made while following along with the Ray Wenderlich book ["Combine: Asynchronous Programming with Swift"](https://store.raywenderlich.com/products/combine-asynchronous-programming-with-swift)._
4 |
5 |
6 | # Contents
7 |
8 | - [Chapter 2: Queues & Threads](./02-publishers-and-subscribers)
9 | - Creating publishers and subscribing to them.
10 | - Subjects
11 | - Dynamically Adjusting Demand
12 | - Type Erasure
13 | - **🥅 Challenge:** Create a Blackjack Card Dealer
14 |
15 |
16 | - [Chapter 8: Combine in Practice: Building a Photo Collage App](./08-photo-collage-app)
17 | - Using Combine publishers in custom views.
18 | - Handling user events with Combine.
19 | - Navigating between views and exchanging data via publishers.
20 | - Using a variety of operators to create different subscriptions to implement your app's logic.
21 | - Wrapping existing Cocoa APIs so you can conveniently use them in your Combine code.
22 |
23 |
24 | - Chapters 9-14: Networking with Combine
25 |
26 |
27 | - [Chapters 15-19: Bulding an App with SwiftUI and Combine Networking](./15-19-bitcoin-average-api-app)
28 |
29 |
30 | - [Chapter 16: Error Handling](./16-error-handling)
31 |
32 |
33 | - [Chapter 17: Schedulers](./17-schedulers)
34 |
35 |
36 | - [Chapter 18: Custom Publishers & Handling Backpressure](./18-custom-publishers-and-handling-backpressure)
37 |
38 |
39 | - [Chapter 19: Testing Combine Code](./19-testing-combine-code)
40 |
41 |
42 | - [Chapter 20: Building A Complete App with Combine, SwiftUI, and CoreData](./20-building-a-complete-app)
43 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/TranslationService/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: "TranslationService",
8 | platforms: [
9 | .iOS(.v13),
10 | .macOS(.v10_15),
11 | ],
12 | products: [
13 | // Products define the executables and libraries produced by a package, and make them visible to other packages.
14 | .library(
15 | name: "TranslationService",
16 | targets: ["TranslationService"]),
17 | ],
18 | dependencies: [
19 | // Dependencies declare other packages that this package depends on.
20 | .package(path: "../Common"),
21 |
22 | .package(url: "https://github.com/CypherPoet/CypherPoetNetStack.git", from: "0.0.28"),
23 | ],
24 | targets: [
25 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
26 | // Targets can depend on other targets in this package, and on products in packages which this package depends on.
27 | .target(
28 | name: "TranslationService",
29 | dependencies: [
30 | "Common",
31 | "CypherPoetNetStack",
32 | ],
33 | path: "Sources/"
34 | ),
35 |
36 |
37 | .testTarget(
38 | name: "TranslationServiceTests",
39 | dependencies: [
40 | "TranslationService"
41 | ],
42 | path: "Tests/TranslationService"
43 | ),
44 | ]
45 | )
46 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/NumberFacts/NumberFacts/App/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // NumberFacts
4 | //
5 | // Created by Brian Sipple on 2/14/20.
6 | // Copyright © 2020 CypherPoet. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import CoreData
11 |
12 | @UIApplicationMain
13 | class AppDelegate: UIResponder, UIApplicationDelegate {
14 |
15 |
16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
17 | // Override point for customization after application launch.
18 | CurrentApp.coreDataManager.setup()
19 |
20 | return true
21 | }
22 |
23 |
24 | // MARK: UISceneSession Lifecycle
25 |
26 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
27 | // Called when a new scene session is being created.
28 | // Use this method to select a configuration to create the new scene with.
29 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
30 | }
31 |
32 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
33 | // Called when the user discards a scene session.
34 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
35 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
36 | }
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/NumbersAPIService/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: "NumbersAPIService",
8 | platforms: [
9 | .iOS(.v13),
10 | .macOS(.v10_15),
11 | ],
12 | products: [
13 | // Products define the executables and libraries produced by a package, and make them visible to other packages.
14 | .library(
15 | name: "NumbersAPIService",
16 | targets: [
17 | "NumbersAPIService",
18 | ]
19 | ),
20 | ],
21 | dependencies: [
22 | // Dependencies declare other packages that this package depends on.
23 | .package(path: "../Common"),
24 |
25 | .package(url: "https://github.com/CypherPoet/CypherPoetNetStack.git", from: "0.0.28"),
26 | ],
27 | targets: [
28 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
29 | // Targets can depend on other targets in this package, and on products in packages which this package depends on.
30 | .target(
31 | name: "NumbersAPIService",
32 | dependencies: [
33 | "Common",
34 | "CypherPoetNetStack",
35 | ],
36 | path: "Sources/"
37 | ),
38 |
39 | .testTarget(
40 | name: "NumbersAPIServiceTests",
41 | dependencies: [
42 | "NumbersAPIService",
43 | ],
44 | path: "Tests/NumbersAPIService"
45 | ),
46 | ]
47 | )
48 |
--------------------------------------------------------------------------------
/15-19-bitcoin-average-api-app/BitcoinAverageAPIFetcher/BitcoinAverageAPIFetcher/Scenes/Settings/SettingsViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SettingsViewModel.swift
3 | // BitcoinAverageAPIFetcher
4 | //
5 | // Created by CypherPoet on 11/15/19.
6 | // ✌️
7 | //
8 |
9 |
10 | import SwiftUI
11 | import Combine
12 | import SatoshiVSKit
13 |
14 |
15 | final class SettingsViewModel: ObservableObject {
16 | private var subscriptions = Set()
17 |
18 | let store: AppStore
19 |
20 |
21 | // MARK: - Published Properties
22 | @Published var filteredShitcoins: [Shitcoin] = []
23 |
24 |
25 | // MARK: - Init
26 | init(store: AppStore) {
27 | self.store = store
28 |
29 | setupSubscribers()
30 | }
31 | }
32 |
33 |
34 | // MARK: - Publishers
35 | extension SettingsViewModel {
36 |
37 | private var settingsStatePublisher: AnyPublisher {
38 | store.$state
39 | .map(\.settingsState)
40 | .eraseToAnyPublisher()
41 | }
42 |
43 |
44 | private var filteredShitcoinsPublisher: AnyPublisher<[Shitcoin], Never> {
45 | settingsStatePublisher
46 | .map(\.filteredShitcoins)
47 | .eraseToAnyPublisher()
48 | }
49 | }
50 |
51 |
52 | // MARK: - Computeds
53 | extension SettingsViewModel {
54 | }
55 |
56 |
57 | // MARK: - Public Methods
58 | extension SettingsViewModel {
59 | }
60 |
61 |
62 |
63 | // MARK: - Private Helpers
64 | private extension SettingsViewModel {
65 |
66 | func setupSubscribers() {
67 | filteredShitcoinsPublisher
68 | .receive(on: DispatchQueue.main)
69 | .assign(to: \.filteredShitcoins, on: self)
70 | .store(in: &subscriptions)
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/06-time-based-operators/Playgrounds/Chapter 6.playground/Pages/Holding Off On Events - Debonuce.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | //: [Previous](@previous)
2 |
3 | import Combine
4 | import SwiftUI
5 | import PlaygroundSupport
6 |
7 |
8 | let debounceTime: TimeInterval = 1.0
9 |
10 |
11 |
12 | // MARK: - Publishers
13 |
14 | let basePublisher = PassthroughSubject()
15 |
16 |
17 | let debouncedPublisher = basePublisher
18 | .debounce(for: .seconds(debounceTime), scheduler: DispatchQueue.main)
19 | // Use `share()` to create a single subscription point to `debounce`
20 | // that will show the same results at the same time to all subscribers
21 | .share()
22 |
23 |
24 |
25 | // MARK: - Subscribers
26 |
27 | let subscription1 = basePublisher
28 | .sink { string in
29 | print("+\(deltaTime)s -- Subject emitted: \(string)")
30 | }
31 |
32 |
33 | let subscription2 = debouncedPublisher
34 | .sink { string in
35 | print("+\(deltaTime)s -- Subject emitted: \(string)")
36 | }
37 |
38 |
39 |
40 | // MARK: - Timelines
41 |
42 | let baseTimeline = TimelineView(
43 | title: "Emitted Values",
44 | events: []
45 | )
46 |
47 |
48 | let debounceTimeline = TimelineView(
49 | title: "Debounced Values (debounce time: \(debounceTime) seconds)",
50 | events: []
51 | )
52 |
53 |
54 |
55 | // MARK: - View Setup
56 |
57 | let view = VStack(spacing: 50) {
58 | baseTimeline
59 | debounceTimeline
60 | }
61 |
62 |
63 | PlaygroundPage.current.liveView = UIHostingController(rootView: view)
64 |
65 |
66 | basePublisher.displayEvents(in: baseTimeline)
67 | debouncedPublisher.displayEvents(in: debounceTimeline)
68 |
69 |
70 |
71 | // 💥
72 |
73 | basePublisher.feed(with: typingHelloWorld)
74 |
75 |
76 | //: [Next](@next)
77 |
--------------------------------------------------------------------------------
/08-photo-collage-app/Collage/Collage/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/19-testing-combine-code/Projects/ColorCalc/ColorCalc/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/final/ChuckNorrisJokes/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/NumberFacts/NumberFacts/NumberFacts/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/06-time-based-operators/Playgrounds/Chapter 6.playground/Pages/Challenge.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | //: [Previous](@previous)
2 |
3 | import Foundation
4 | import Combine
5 |
6 | //: Challenge:
7 | //:
8 | //: Starting with:
9 | //:
10 | //: - A subject that emits integers.
11 | //: - A function call that feeds the subject with mysterious data.
12 | //:
13 | //: Your challenge is to:
14 | //:
15 | //: - Group data by batches of 0.5 seconds.
16 | //: - Turn the grouped data into a string.
17 | //: - If there is a pause longer than 0.9 seconds in the feed, print the 👏 emoji.
18 | //: - Print it.
19 | //:
20 | //:
21 | //: Hint: Create a second publisher for this step and merge it
22 | //: with the first publisher in your subscription.
23 | //:
24 | //: Note: To convert an Int to a Character, you can do something like Character(Unicode.Scalar(value)!).
25 | //: If you code this challenge correctly, you’ll see a sentence printed in the Debug area. What is it?
26 |
27 |
28 |
29 | let intStream = PassthroughSubject()
30 | let collectionInterval: TimeInterval = 0.5
31 |
32 | var subscriptions = Set()
33 |
34 |
35 | let strings = intStream
36 | .collect(.byTime(
37 | DispatchQueue.main,
38 | .seconds(collectionInterval)
39 | ))
40 | .map({ numbers in
41 | String(numbers.map { Character(Unicode.Scalar($0)!) })
42 | })
43 |
44 |
45 |
46 | let measurements = intStream
47 | .measureInterval(using: DispatchQueue.main)
48 | .compactMap({ stride in
49 | stride > 0.9 ? "👏" : nil
50 | })
51 |
52 |
53 | Publishers.Merge(strings, measurements)
54 | .sink { value in
55 | print(value)
56 | }
57 | .store(in: &subscriptions)
58 |
59 |
60 |
61 | feedValues(to: intStream)
62 |
63 |
64 |
65 | //: [Next](@next)
66 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/starter/ChuckNorrisJokes/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/final/ChuckNorrisJokes/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/20-building-a-complete-app/BookExample/challenge/starter/ChuckNorrisJokes/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/15-19-bitcoin-average-api-app/BitcoinAverageAPIFetcher/BitcoinAverageAPIFetcher/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------