Skip to content

Commit 9e41fd5

Browse files
New case study app showing off swift-dependencies (pointfreeco#61)
* wip * wip * wip * wip * wip * wip * wip * lots of helpers * added a ui test * clean up * wip * wip * clean up * wip * wip * wip * wip * clean up * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * xcode 13 fix * clean up * wip * wip * wip * wip * wip * wip * wip * wip Co-authored-by: Stephen Celis <[email protected]>
1 parent 14bb76a commit 9e41fd5

File tree

46 files changed

+4147
-41
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+4147
-41
lines changed

Examples/Examples.xcodeproj/project.pbxproj

Lines changed: 538 additions & 13 deletions
Large diffs are not rendered by default.

Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 35 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Scheme
3+
LastUpgradeVersion = "1410"
4+
version = "1.3">
5+
<BuildAction
6+
parallelizeBuildables = "YES"
7+
buildImplicitDependencies = "YES">
8+
<BuildActionEntries>
9+
<BuildActionEntry
10+
buildForTesting = "YES"
11+
buildForRunning = "YES"
12+
buildForProfiling = "YES"
13+
buildForArchiving = "YES"
14+
buildForAnalyzing = "YES">
15+
<BuildableReference
16+
BuildableIdentifier = "primary"
17+
BlueprintIdentifier = "DC5E07732947CCD700293F45"
18+
BuildableName = "Standups.app"
19+
BlueprintName = "Standups"
20+
ReferencedContainer = "container:Examples.xcodeproj">
21+
</BuildableReference>
22+
</BuildActionEntry>
23+
</BuildActionEntries>
24+
</BuildAction>
25+
<TestAction
26+
buildConfiguration = "Debug"
27+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
28+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
29+
shouldUseLaunchSchemeArgsEnv = "YES"
30+
codeCoverageEnabled = "YES">
31+
<Testables>
32+
<TestableReference
33+
skipped = "NO">
34+
<BuildableReference
35+
BuildableIdentifier = "primary"
36+
BlueprintIdentifier = "DC5E07822947CCD800293F45"
37+
BuildableName = "StandupsTests.xctest"
38+
BlueprintName = "StandupsTests"
39+
ReferencedContainer = "container:Examples.xcodeproj">
40+
</BuildableReference>
41+
</TestableReference>
42+
<TestableReference
43+
skipped = "YES">
44+
<BuildableReference
45+
BuildableIdentifier = "primary"
46+
BlueprintIdentifier = "CA53F7F9295BEDBD00DE68FE"
47+
BuildableName = "StandupsUITests.xctest"
48+
BlueprintName = "StandupsUITests"
49+
ReferencedContainer = "container:Examples.xcodeproj">
50+
</BuildableReference>
51+
</TestableReference>
52+
</Testables>
53+
</TestAction>
54+
<LaunchAction
55+
buildConfiguration = "Debug"
56+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
57+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
58+
launchStyle = "0"
59+
useCustomWorkingDirectory = "NO"
60+
ignoresPersistentStateOnLaunch = "NO"
61+
debugDocumentVersioning = "YES"
62+
debugServiceExtension = "internal"
63+
allowLocationSimulation = "YES">
64+
<BuildableProductRunnable
65+
runnableDebuggingMode = "0">
66+
<BuildableReference
67+
BuildableIdentifier = "primary"
68+
BlueprintIdentifier = "DC5E07732947CCD700293F45"
69+
BuildableName = "Standups.app"
70+
BlueprintName = "Standups"
71+
ReferencedContainer = "container:Examples.xcodeproj">
72+
</BuildableReference>
73+
</BuildableProductRunnable>
74+
</LaunchAction>
75+
<ProfileAction
76+
buildConfiguration = "Release"
77+
shouldUseLaunchSchemeArgsEnv = "YES"
78+
savedToolIdentifier = ""
79+
useCustomWorkingDirectory = "NO"
80+
debugDocumentVersioning = "YES">
81+
<BuildableProductRunnable
82+
runnableDebuggingMode = "0">
83+
<BuildableReference
84+
BuildableIdentifier = "primary"
85+
BlueprintIdentifier = "DC5E07732947CCD700293F45"
86+
BuildableName = "Standups.app"
87+
BlueprintName = "Standups"
88+
ReferencedContainer = "container:Examples.xcodeproj">
89+
</BuildableReference>
90+
</BuildableProductRunnable>
91+
</ProfileAction>
92+
<AnalyzeAction
93+
buildConfiguration = "Debug">
94+
</AnalyzeAction>
95+
<ArchiveAction
96+
buildConfiguration = "Release"
97+
revealArchiveInOrganizer = "YES">
98+
</ArchiveAction>
99+
</Scheme>

Examples/Standups/Readme.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Standups
2+
3+
This project demonstrates how to build a complex, real world application that deals with many forms
4+
of navigation (_e.g._, sheets, drill-downs, alerts), many side effects (timers, speech recognizer,
5+
data persistence), and do so in a way that is testable and modular.
6+
7+
This application was built over the course of [many episodes][modern-swiftui-collection] on
8+
Point-Free, a video series exploring functional programming and the Swift language, hosted by
9+
[Brandon Williams](https://twitter.com/mbrandonw) and [Stephen
10+
Celis](https://twitter.com/stephencelis).
11+
12+
<a href="https://www.pointfree.co/collections/swiftui/modern-swiftui">
13+
<img alt="video poster image" src="https://d3rccdn33rt8ze.cloudfront.net/episodes/0209.jpeg" width="600">
14+
</a>
15+
16+
## Overview
17+
18+
The inspiration for this application comes Apple's [Scrumdinger][scrumdinger] tutorial:
19+
20+
> This module guides you through the development of Scrumdinger, an iOS app that helps users manage
21+
> their daily scrums. To help keep scrums short and focused, Scrumdinger uses visual and audio cues
22+
> to indicate when and how long each attendee should speak. The app also displays a progress screen
23+
> that shows the time remaining in the meeting and creates a transcript that users can refer to
24+
> later.
25+
26+
The Scrumdinger app is one of Apple's most interesting code samples as it deals with many real world
27+
world problems that one faces in application development. It shows off many types of navigation,
28+
it deals with complex effects such as timers and speech recognition, and it persists application
29+
to disk.
30+
31+
However, it is not necessarily built in the most ideal way. It uses mostly fire-and-forget style
32+
navigation, which means you can't easily deep link into any screen of the app, which is handy for
33+
push notifications and opening URLs. It also uses uncontrolled dependencies, including file system
34+
access, timers and a speech recognizer, which makes it nearly impossible to write automated tests
35+
and even hinders the ability to preview the app in Xcode previews.
36+
37+
But, the simplicity of Apple's Scrumdinger codebase is not a defect. In fact, it's a feature!
38+
Apple's sample code is viewed by hundreds of thousands of developers across the world, and so its
39+
goal is to be as approachable as possible in order to teach the basics of SwiftUI. But, that doesn't
40+
mean there isn't room for improvement.
41+
42+
## Modern SwiftUI
43+
44+
Our Standups application is a rebuild of Apple's Scrumdinger application, but with a focus on
45+
modern, best practices for SwiftUI development. We faithfully recreate the Scrumdinger, but with
46+
some key additions:
47+
48+
1. Identifiers are made type safe using our [Tagged library][tagged-gh]. This prevents us from
49+
writing non-sensical code, such as comparing a `Standup.ID` to a `Attendee.ID`.
50+
2. Instead of using bare arrays in feature logic we use an "identified" array from our
51+
[IdentifiedCollections][identified-collections-gh] library. This allows you to read and modify
52+
elements of the collection via their ID rather than positional index, which can be error prone
53+
and lead to bugs or crashes.
54+
3. _All_ navigation is driven off of state, including sheets, drill-downs and alerts. This makes
55+
it possible to deep link into any screen of the app by just constructing a piece of state and
56+
handing it off to SwiftUI.
57+
4. Further, each view represents its navigation destinations as a single enum, which gives us
58+
compile time proof that two destinations cannot be active at the same time. This cannot be
59+
accomplished with default SwiftUI tools, but can be done with our [SwiftUINavigation
60+
library][swiftui-nav-gh].
61+
5. All side effects are controlled. This includes access to the file system for persistence, access
62+
to time-based asynchrony for timers, access to speech recognition APIs, and even the creation
63+
of dates and UUIDs. This allows us to run our application in specific execution contexts, which
64+
is very useful in tests and Xcode previews. We accomplish this using our
65+
[Dependencies][dependencies-gh] library.
66+
6. The project includes a full test suite. Since all of navigation is driven off of state, and
67+
because we controlled all dependencies, we can write very comprehensive and nuanced tests. For
68+
example, we can write a unit test that proves that when a standup meeting's timer runs out the
69+
screen pops off the stack and a new transcript is added to the standup. Such a test would be
70+
very difficult, if not impossible, without controlling dependencies.
71+
72+
[modern-swiftui-collection]: https://www.pointfree.co/collections/swiftui/modern-swiftui
73+
[scrumdinger]: https://developer.apple.com/tutorials/app-dev-training/getting-started-with-scrumdinger
74+
[tagged-gh]: http://github.com/pointfreeco/swift-tagged
75+
[identified-collections-gh]: http://github.com/pointfreeco/swift-identified-collections
76+
[swiftui-nav-gh]: http://github.com/pointfreeco/swiftui-navigation
77+
[dependencies-gh]: http://github.com/pointfreeco/swift-dependencies
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"colors" : [
3+
{
4+
"idiom" : "universal"
5+
}
6+
],
7+
"info" : {
8+
"author" : "xcode",
9+
"version" : 1
10+
}
11+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"images" : [
3+
{
4+
"idiom" : "universal",
5+
"platform" : "ios",
6+
"size" : "1024x1024"
7+
}
8+
],
9+
"info" : {
10+
"author" : "xcode",
11+
"version" : 1
12+
}
13+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"info" : {
3+
"author" : "xcode",
4+
"version" : 1
5+
}
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"info" : {
3+
"author" : "xcode",
4+
"version" : 1
5+
}
6+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"colors" : [
3+
{
4+
"color" : {
5+
"color-space" : "srgb",
6+
"components" : {
7+
"alpha" : "1.000",
8+
"blue" : "0.820",
9+
"green" : "0.502",
10+
"red" : "0.933"
11+
}
12+
},
13+
"idiom" : "universal"
14+
},
15+
{
16+
"appearances" : [
17+
{
18+
"appearance" : "luminosity",
19+
"value" : "dark"
20+
}
21+
],
22+
"color" : {
23+
"color-space" : "srgb",
24+
"components" : {
25+
"alpha" : "1.000",
26+
"blue" : "0.820",
27+
"green" : "0.502",
28+
"red" : "0.933"
29+
}
30+
},
31+
"idiom" : "universal"
32+
}
33+
],
34+
"info" : {
35+
"author" : "xcode",
36+
"version" : 1
37+
}
38+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"colors" : [
3+
{
4+
"color" : {
5+
"color-space" : "srgb",
6+
"components" : {
7+
"alpha" : "1.000",
8+
"blue" : "0.588",
9+
"green" : "0.945",
10+
"red" : "1.000"
11+
}
12+
},
13+
"idiom" : "universal"
14+
},
15+
{
16+
"appearances" : [
17+
{
18+
"appearance" : "luminosity",
19+
"value" : "dark"
20+
}
21+
],
22+
"color" : {
23+
"color-space" : "srgb",
24+
"components" : {
25+
"alpha" : "1.000",
26+
"blue" : "0.588",
27+
"green" : "0.945",
28+
"red" : "1.000"
29+
}
30+
},
31+
"idiom" : "universal"
32+
}
33+
],
34+
"info" : {
35+
"author" : "xcode",
36+
"version" : 1
37+
}
38+
}

0 commit comments

Comments
 (0)