-
Notifications
You must be signed in to change notification settings - Fork 299
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ea63fe0
commit 4cdd204
Showing
13 changed files
with
1,149 additions
and
0 deletions.
There are no files selected for viewing
505 changes: 505 additions & 0 deletions
505
0154-refreshable-pt2/CaseStudies/CaseStudies.xcodeproj/project.pbxproj
Large diffs are not rendered by default.
Oops, something went wrong.
7 changes: 7 additions & 0 deletions
7
...shable-pt2/CaseStudies/CaseStudies.xcodeproj/project.xcworkspace/contents.xcworkspacedata
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
8 changes: 8 additions & 0 deletions
8
...seStudies/CaseStudies.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
<plist version="1.0"> | ||
<dict> | ||
<key>IDEDidComputeMac32BitWarning</key> | ||
<true/> | ||
</dict> | ||
</plist> |
88 changes: 88 additions & 0 deletions
88
...2/CaseStudies/CaseStudies.xcodeproj/xcshareddata/xcschemes/CaseStudies (SwiftUI).xcscheme
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<Scheme | ||
LastUpgradeVersion = "1240" | ||
version = "1.3"> | ||
<BuildAction | ||
parallelizeBuildables = "YES" | ||
buildImplicitDependencies = "YES"> | ||
<BuildActionEntries> | ||
<BuildActionEntry | ||
buildForTesting = "YES" | ||
buildForRunning = "YES" | ||
buildForProfiling = "YES" | ||
buildForArchiving = "YES" | ||
buildForAnalyzing = "YES"> | ||
<BuildableReference | ||
BuildableIdentifier = "primary" | ||
BlueprintIdentifier = "DC89C41224460F95006900B9" | ||
BuildableName = "SwiftUICaseStudies.app" | ||
BlueprintName = "SwiftUICaseStudies" | ||
ReferencedContainer = "container:CaseStudies.xcodeproj"> | ||
</BuildableReference> | ||
</BuildActionEntry> | ||
</BuildActionEntries> | ||
</BuildAction> | ||
<TestAction | ||
buildConfiguration = "Debug" | ||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" | ||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" | ||
shouldUseLaunchSchemeArgsEnv = "YES"> | ||
<Testables> | ||
<TestableReference | ||
skipped = "NO"> | ||
<BuildableReference | ||
BuildableIdentifier = "primary" | ||
BlueprintIdentifier = "DC89C42824460F96006900B9" | ||
BuildableName = "SwiftUICaseStudiesTests.xctest" | ||
BlueprintName = "SwiftUICaseStudiesTests" | ||
ReferencedContainer = "container:CaseStudies.xcodeproj"> | ||
</BuildableReference> | ||
</TestableReference> | ||
</Testables> | ||
</TestAction> | ||
<LaunchAction | ||
buildConfiguration = "Debug" | ||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" | ||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" | ||
launchStyle = "0" | ||
useCustomWorkingDirectory = "NO" | ||
ignoresPersistentStateOnLaunch = "NO" | ||
debugDocumentVersioning = "YES" | ||
debugServiceExtension = "internal" | ||
allowLocationSimulation = "YES"> | ||
<BuildableProductRunnable | ||
runnableDebuggingMode = "0"> | ||
<BuildableReference | ||
BuildableIdentifier = "primary" | ||
BlueprintIdentifier = "DC89C41224460F95006900B9" | ||
BuildableName = "SwiftUICaseStudies.app" | ||
BlueprintName = "SwiftUICaseStudies" | ||
ReferencedContainer = "container:CaseStudies.xcodeproj"> | ||
</BuildableReference> | ||
</BuildableProductRunnable> | ||
</LaunchAction> | ||
<ProfileAction | ||
buildConfiguration = "Release" | ||
shouldUseLaunchSchemeArgsEnv = "YES" | ||
savedToolIdentifier = "" | ||
useCustomWorkingDirectory = "NO" | ||
debugDocumentVersioning = "YES"> | ||
<BuildableProductRunnable | ||
runnableDebuggingMode = "0"> | ||
<BuildableReference | ||
BuildableIdentifier = "primary" | ||
BlueprintIdentifier = "DC89C41224460F95006900B9" | ||
BuildableName = "SwiftUICaseStudies.app" | ||
BlueprintName = "SwiftUICaseStudies" | ||
ReferencedContainer = "container:CaseStudies.xcodeproj"> | ||
</BuildableReference> | ||
</BuildableProductRunnable> | ||
</ProfileAction> | ||
<AnalyzeAction | ||
buildConfiguration = "Debug"> | ||
</AnalyzeAction> | ||
<ArchiveAction | ||
buildConfiguration = "Release" | ||
revealArchiveInOrganizer = "YES"> | ||
</ArchiveAction> | ||
</Scheme> |
Binary file added
BIN
+7.96 KB
...2/CaseStudies/SwiftUICaseStudies/Assets.xcassets/AppIcon.appiconset/AppIcon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
99 changes: 99 additions & 0 deletions
99
...hable-pt2/CaseStudies/SwiftUICaseStudies/Assets.xcassets/AppIcon.appiconset/Contents.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
{ | ||
"images" : [ | ||
{ | ||
"idiom" : "iphone", | ||
"scale" : "2x", | ||
"size" : "20x20" | ||
}, | ||
{ | ||
"idiom" : "iphone", | ||
"scale" : "3x", | ||
"size" : "20x20" | ||
}, | ||
{ | ||
"idiom" : "iphone", | ||
"scale" : "2x", | ||
"size" : "29x29" | ||
}, | ||
{ | ||
"idiom" : "iphone", | ||
"scale" : "3x", | ||
"size" : "29x29" | ||
}, | ||
{ | ||
"idiom" : "iphone", | ||
"scale" : "2x", | ||
"size" : "40x40" | ||
}, | ||
{ | ||
"idiom" : "iphone", | ||
"scale" : "3x", | ||
"size" : "40x40" | ||
}, | ||
{ | ||
"idiom" : "iphone", | ||
"scale" : "2x", | ||
"size" : "60x60" | ||
}, | ||
{ | ||
"filename" : "AppIcon.png", | ||
"idiom" : "iphone", | ||
"scale" : "3x", | ||
"size" : "60x60" | ||
}, | ||
{ | ||
"idiom" : "ipad", | ||
"scale" : "1x", | ||
"size" : "20x20" | ||
}, | ||
{ | ||
"idiom" : "ipad", | ||
"scale" : "2x", | ||
"size" : "20x20" | ||
}, | ||
{ | ||
"idiom" : "ipad", | ||
"scale" : "1x", | ||
"size" : "29x29" | ||
}, | ||
{ | ||
"idiom" : "ipad", | ||
"scale" : "2x", | ||
"size" : "29x29" | ||
}, | ||
{ | ||
"idiom" : "ipad", | ||
"scale" : "1x", | ||
"size" : "40x40" | ||
}, | ||
{ | ||
"idiom" : "ipad", | ||
"scale" : "2x", | ||
"size" : "40x40" | ||
}, | ||
{ | ||
"idiom" : "ipad", | ||
"scale" : "1x", | ||
"size" : "76x76" | ||
}, | ||
{ | ||
"idiom" : "ipad", | ||
"scale" : "2x", | ||
"size" : "76x76" | ||
}, | ||
{ | ||
"idiom" : "ipad", | ||
"scale" : "2x", | ||
"size" : "83.5x83.5" | ||
}, | ||
{ | ||
"idiom" : "ios-marketing", | ||
"scale" : "1x", | ||
"size" : "1024x1024" | ||
} | ||
], | ||
"info" : { | ||
"author" : "xcode", | ||
"version" : 1 | ||
} | ||
} |
6 changes: 6 additions & 0 deletions
6
0154-refreshable-pt2/CaseStudies/SwiftUICaseStudies/Assets.xcassets/Contents.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"info" : { | ||
"author" : "xcode", | ||
"version" : 1 | ||
} | ||
} |
128 changes: 128 additions & 0 deletions
128
0154-refreshable-pt2/CaseStudies/SwiftUICaseStudies/ComposableRefreshable.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
import Combine | ||
import ComposableArchitecture | ||
import SwiftUI | ||
|
||
struct PullToRefreshState: Equatable { | ||
var count = 0 | ||
var fact: String? | ||
var isLoading = false | ||
} | ||
|
||
enum PullToRefreshAction: Equatable { | ||
case cancelButtonTapped | ||
case factResponse(Result<String, FactClient.Error>) | ||
case decrementButtonTapped | ||
case incrementButtonTapped | ||
case refresh | ||
} | ||
|
||
struct PullToRefreshEnvironment { | ||
var fact: FactClient | ||
var mainQueue: AnySchedulerOf<DispatchQueue> | ||
} | ||
|
||
let pullToRefreshReducer = Reducer< | ||
PullToRefreshState, | ||
PullToRefreshAction, | ||
PullToRefreshEnvironment | ||
> { state, action, environment in | ||
|
||
struct CancelId: Hashable {} | ||
|
||
switch action { | ||
case .cancelButtonTapped: | ||
state.isLoading = false | ||
return .cancel(id: CancelId()) | ||
|
||
case let .factResponse(.success(fact)): | ||
state.isLoading = false | ||
state.fact = fact | ||
return .none | ||
|
||
case .factResponse(.failure): | ||
state.isLoading = false | ||
// TODO: do some error handling | ||
return .none | ||
|
||
case .decrementButtonTapped: | ||
state.count -= 1 | ||
return .none | ||
|
||
case .incrementButtonTapped: | ||
state.count += 1 | ||
return .none | ||
|
||
case .refresh: | ||
state.fact = nil | ||
state.isLoading = true | ||
return environment.fact.fetch(state.count) | ||
// .receive(on: environment.mainQueue) | ||
.delay(for: 2, scheduler: environment.mainQueue.animation()) | ||
.catchToEffect() | ||
.map(PullToRefreshAction.factResponse) | ||
.cancellable(id: CancelId()) | ||
} | ||
} | ||
|
||
struct PullToRefreshView: View { | ||
let store: Store<PullToRefreshState, PullToRefreshAction> | ||
|
||
var body: some View { | ||
WithViewStore(self.store) { viewStore in | ||
List { | ||
HStack { | ||
Button("-") { viewStore.send(.decrementButtonTapped) } | ||
Text("\(viewStore.count)") | ||
Button("+") { viewStore.send(.incrementButtonTapped) } | ||
} | ||
.buttonStyle(.plain) | ||
|
||
if let fact = viewStore.fact { | ||
Text(fact) | ||
} | ||
if viewStore.isLoading { | ||
Button("Cancel") { | ||
viewStore.send(.cancelButtonTapped, animation: .default) | ||
} | ||
} | ||
} | ||
.refreshable { | ||
await viewStore.send(.refresh, while: \.isLoading) | ||
} | ||
} | ||
} | ||
} | ||
|
||
extension ViewStore { | ||
func send( | ||
_ action: Action, | ||
while predicate: @escaping (State) -> Bool | ||
) async { | ||
self.send(action) | ||
await withUnsafeContinuation { (continuation: UnsafeContinuation<Void, Never>) in | ||
var cancellable: Cancellable? | ||
cancellable = self.publisher | ||
.filter { !predicate($0) } | ||
.prefix(1) | ||
.sink { _ in | ||
continuation.resume() | ||
_ = cancellable | ||
} | ||
} | ||
} | ||
} | ||
|
||
struct PullToRefresh_Previews: PreviewProvider { | ||
static var previews: some View { | ||
PullToRefreshView( | ||
store: .init( | ||
initialState: .init(), | ||
reducer: pullToRefreshReducer, | ||
environment: PullToRefreshEnvironment( | ||
fact: .live, | ||
mainQueue: .main | ||
) | ||
) | ||
) | ||
} | ||
} |
42 changes: 42 additions & 0 deletions
42
0154-refreshable-pt2/CaseStudies/SwiftUICaseStudies/FactClient.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import Combine | ||
import ComposableArchitecture | ||
import XCTestDynamicOverlay | ||
|
||
struct FactClient { | ||
var fetch: (Int) -> Effect<String, Error> | ||
|
||
struct Error: Swift.Error, Equatable {} | ||
} | ||
|
||
extension FactClient { | ||
// This is the "live" fact dependency that reaches into the outside world to fetch trivia. | ||
// Typically this live implementation of the dependency would live in its own module so that the | ||
// main feature doesn't need to compile it. | ||
static let live = Self( | ||
fetch: { number in | ||
URLSession.shared.dataTaskPublisher( | ||
for: URL(string: "http://numbersapi.com/\(number)/trivia")! | ||
) | ||
.map { data, _ in String(decoding: data, as: UTF8.self) } | ||
.catch { _ in | ||
// Sometimes numbersapi.com can be flakey, so if it ever fails we will just | ||
// default to a mock response. | ||
Just("\(number) is a good number Brent") | ||
.delay(for: 1, scheduler: DispatchQueue.main) | ||
} | ||
.setFailureType(to: Error.self) | ||
.eraseToEffect() | ||
}) | ||
} | ||
|
||
#if DEBUG | ||
extension FactClient { | ||
// This is the "failing" fact dependency that is useful to plug into tests that you want | ||
// to prove do not need the dependency. | ||
static let failing = Self( | ||
fetch: { _ in | ||
XCTFail("\(Self.self).fact is unimplemented.") | ||
return .none | ||
}) | ||
} | ||
#endif |
Oops, something went wrong.