├── HandlingUserInput.xcodeproj
├── .xcodesamplecode.plist
├── project.pbxproj
├── project.xcworkspace
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── WorkspaceSettings.xcsettings
└── xcuserdata
│ └── ademedetskii.xcuserdatad
│ ├── xcdebugger
│ └── Breakpoints_v2.xcbkptlist
│ └── xcschemes
│ └── xcschememanagement.plist
├── Landmarks
├── Actions
│ ├── ToggleFavoritesOnly.swift
│ └── ToggleLandmarkFavorite.swift
├── AppDelegate.swift
├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── landmark_app_icon_1024x1024.png
│ │ ├── landmark_app_icon_120x120-1.png
│ │ ├── landmark_app_icon_120x120.png
│ │ ├── landmark_app_icon_152x152.png
│ │ ├── landmark_app_icon_167x167.png
│ │ ├── landmark_app_icon_180x180.png
│ │ ├── landmark_app_icon_40x40-1.png
│ │ ├── landmark_app_icon_40x40-2.png
│ │ ├── landmark_app_icon_40x40.png
│ │ ├── landmark_app_icon_58x58-1.png
│ │ ├── landmark_app_icon_58x58.png
│ │ ├── landmark_app_icon_76x76.png
│ │ ├── landmark_app_icon_80x80-1.png
│ │ ├── landmark_app_icon_80x80.png
│ │ └── landmark_app_icon_87x87.png
│ ├── Contents.json
│ └── turtlerock.imageset
│ │ ├── Contents.json
│ │ └── turtlerock.jpg
├── Base.lproj
│ └── LaunchScreen.storyboard
├── Info.plist
├── Models
│ ├── Data.swift
│ ├── Landmark.swift
│ └── UserData.swift
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
├── Resources
│ ├── charleyrivers.jpg
│ ├── chilkoottrail.jpg
│ ├── chincoteague.jpg
│ ├── hiddenlake.jpg
│ ├── icybay.jpg
│ ├── lakemcdonald.jpg
│ ├── landmarkData.json
│ ├── rainbowlake.jpg
│ ├── silversalmoncreek.jpg
│ ├── stmarylake.jpg
│ ├── turtlerock.jpg
│ ├── twinlake.jpg
│ └── umbagog.jpg
├── SceneDelegate.swift
├── State.swift
├── Supporting Views
│ ├── CircleImage.swift
│ └── MapView.swift
└── Views
│ ├── LandmarkDetail.swift
│ ├── LandmarkList.swift
│ └── LandmarkRow.swift
└── Redux
├── Action.swift
├── ConnectedView.swift
├── Reducable.swift
├── Reducer.swift
├── Store.swift
├── StoreConnector.swift
└── StoreProvider.swift
/HandlingUserInput.xcodeproj/.xcodesamplecode.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/HandlingUserInput.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | B7394866229F194000C47603 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7394865229F194000C47603 /* AppDelegate.swift */; };
11 | B7394868229F194000C47603 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7394867229F194000C47603 /* SceneDelegate.swift */; };
12 | B739486A229F194000C47603 /* LandmarkDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7394869229F194000C47603 /* LandmarkDetail.swift */; };
13 | B739486C229F194200C47603 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B739486B229F194200C47603 /* Assets.xcassets */; };
14 | B739486F229F194200C47603 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B739486E229F194200C47603 /* Preview Assets.xcassets */; };
15 | B7394872229F194200C47603 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B7394870229F194200C47603 /* LaunchScreen.storyboard */; };
16 | B739487A229F1B3F00C47603 /* CircleImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7394879229F1B3F00C47603 /* CircleImage.swift */; };
17 | B739487C229F1B6800C47603 /* MapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B739487B229F1B6800C47603 /* MapView.swift */; };
18 | B7394881229F28B900C47603 /* Landmark.swift in Sources */ = {isa = PBXBuildFile; fileRef = B739487F229F28B900C47603 /* Landmark.swift */; };
19 | B7394882229F28B900C47603 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7394880229F28B900C47603 /* Data.swift */; };
20 | B7394891229F292F00C47603 /* rainbowlake.jpg in Resources */ = {isa = PBXBuildFile; fileRef = B7394884229F292D00C47603 /* rainbowlake.jpg */; };
21 | B7394893229F292F00C47603 /* icybay.jpg in Resources */ = {isa = PBXBuildFile; fileRef = B7394886229F292E00C47603 /* icybay.jpg */; };
22 | B7394894229F292F00C47603 /* lakemcdonald.jpg in Resources */ = {isa = PBXBuildFile; fileRef = B7394887229F292E00C47603 /* lakemcdonald.jpg */; };
23 | B7394895229F292F00C47603 /* turtlerock.jpg in Resources */ = {isa = PBXBuildFile; fileRef = B7394888229F292E00C47603 /* turtlerock.jpg */; };
24 | B7394896229F292F00C47603 /* umbagog.jpg in Resources */ = {isa = PBXBuildFile; fileRef = B7394889229F292E00C47603 /* umbagog.jpg */; };
25 | B7394897229F292F00C47603 /* hiddenlake.jpg in Resources */ = {isa = PBXBuildFile; fileRef = B739488A229F292E00C47603 /* hiddenlake.jpg */; };
26 | B7394898229F292F00C47603 /* stmarylake.jpg in Resources */ = {isa = PBXBuildFile; fileRef = B739488B229F292E00C47603 /* stmarylake.jpg */; };
27 | B7394899229F292F00C47603 /* twinlake.jpg in Resources */ = {isa = PBXBuildFile; fileRef = B739488C229F292E00C47603 /* twinlake.jpg */; };
28 | B739489A229F292F00C47603 /* silversalmoncreek.jpg in Resources */ = {isa = PBXBuildFile; fileRef = B739488D229F292E00C47603 /* silversalmoncreek.jpg */; };
29 | B739489B229F292F00C47603 /* landmarkData.json in Resources */ = {isa = PBXBuildFile; fileRef = B739488E229F292E00C47603 /* landmarkData.json */; };
30 | B739489C229F292F00C47603 /* chincoteague.jpg in Resources */ = {isa = PBXBuildFile; fileRef = B739488F229F292F00C47603 /* chincoteague.jpg */; };
31 | B739489D229F292F00C47603 /* chilkoottrail.jpg in Resources */ = {isa = PBXBuildFile; fileRef = B7394890229F292F00C47603 /* chilkoottrail.jpg */; };
32 | B739489F229F2D9700C47603 /* LandmarkRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B739489E229F2D9700C47603 /* LandmarkRow.swift */; };
33 | B73948A1229F2E1F00C47603 /* LandmarkList.swift in Sources */ = {isa = PBXBuildFile; fileRef = B73948A0229F2E1F00C47603 /* LandmarkList.swift */; };
34 | B73948A3229F3E2200C47603 /* charleyrivers.jpg in Resources */ = {isa = PBXBuildFile; fileRef = B73948A2229F3E2200C47603 /* charleyrivers.jpg */; };
35 | B7D2AAC5229F4D7C0061E5F5 /* UserData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7D2AAC4229F4D7C0061E5F5 /* UserData.swift */; };
36 | DE03695722AD7FC400C24B7C /* ConnectedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE03695622AD7FC400C24B7C /* ConnectedView.swift */; };
37 | DE03695922AD7FDC00C24B7C /* StoreConnector.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE03695822AD7FDC00C24B7C /* StoreConnector.swift */; };
38 | DE03695B22AD9FDA00C24B7C /* Reducable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE03695A22AD9FDA00C24B7C /* Reducable.swift */; };
39 | DE57486C22AD57E50087D23E /* Reducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE57486B22AD57E50087D23E /* Reducer.swift */; };
40 | DE57486E22AD592A0087D23E /* State.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE57486D22AD592A0087D23E /* State.swift */; };
41 | DE8158E922AAFEB900AAC05E /* StoreProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE8158E822AAFEB900AAC05E /* StoreProvider.swift */; };
42 | DE8158EB22AAFF7F00AAC05E /* Store.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE8158EA22AAFF7F00AAC05E /* Store.swift */; };
43 | DE8158EE22AB00DD00AAC05E /* Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE8158ED22AB00DD00AAC05E /* Action.swift */; };
44 | DE8158F022AB00FB00AAC05E /* ToggleLandmarkFavorite.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE8158EF22AB00FB00AAC05E /* ToggleLandmarkFavorite.swift */; };
45 | DE8158F222AB011B00AAC05E /* ToggleFavoritesOnly.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE8158F122AB011B00AAC05E /* ToggleFavoritesOnly.swift */; };
46 | /* End PBXBuildFile section */
47 |
48 | /* Begin PBXFileReference section */
49 | B7394862229F194000C47603 /* Landmarks.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Landmarks.app; sourceTree = BUILT_PRODUCTS_DIR; };
50 | B7394865229F194000C47603 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
51 | B7394867229F194000C47603 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
52 | B7394869229F194000C47603 /* LandmarkDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LandmarkDetail.swift; sourceTree = ""; };
53 | B739486B229F194200C47603 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
54 | B739486E229F194200C47603 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
55 | B7394871229F194200C47603 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
56 | B7394873229F194200C47603 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
57 | B7394879229F1B3F00C47603 /* CircleImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleImage.swift; sourceTree = ""; };
58 | B739487B229F1B6800C47603 /* MapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapView.swift; sourceTree = ""; };
59 | B739487F229F28B900C47603 /* Landmark.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Landmark.swift; sourceTree = ""; };
60 | B7394880229F28B900C47603 /* Data.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Data.swift; sourceTree = ""; };
61 | B7394884229F292D00C47603 /* rainbowlake.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = rainbowlake.jpg; sourceTree = ""; };
62 | B7394886229F292E00C47603 /* icybay.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = icybay.jpg; sourceTree = ""; };
63 | B7394887229F292E00C47603 /* lakemcdonald.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = lakemcdonald.jpg; sourceTree = ""; };
64 | B7394888229F292E00C47603 /* turtlerock.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = turtlerock.jpg; sourceTree = ""; };
65 | B7394889229F292E00C47603 /* umbagog.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = umbagog.jpg; sourceTree = ""; };
66 | B739488A229F292E00C47603 /* hiddenlake.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = hiddenlake.jpg; sourceTree = ""; };
67 | B739488B229F292E00C47603 /* stmarylake.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = stmarylake.jpg; sourceTree = ""; };
68 | B739488C229F292E00C47603 /* twinlake.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = twinlake.jpg; sourceTree = ""; };
69 | B739488D229F292E00C47603 /* silversalmoncreek.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = silversalmoncreek.jpg; sourceTree = ""; };
70 | B739488E229F292E00C47603 /* landmarkData.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = landmarkData.json; sourceTree = ""; };
71 | B739488F229F292F00C47603 /* chincoteague.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = chincoteague.jpg; sourceTree = ""; };
72 | B7394890229F292F00C47603 /* chilkoottrail.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = chilkoottrail.jpg; sourceTree = ""; };
73 | B739489E229F2D9700C47603 /* LandmarkRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LandmarkRow.swift; sourceTree = ""; };
74 | B73948A0229F2E1F00C47603 /* LandmarkList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LandmarkList.swift; sourceTree = ""; };
75 | B73948A2229F3E2200C47603 /* charleyrivers.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = charleyrivers.jpg; sourceTree = ""; };
76 | B7D2AAC4229F4D7C0061E5F5 /* UserData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserData.swift; sourceTree = ""; };
77 | C4E4AAA0C4E4035000000001 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; };
78 | D5436C00D5426EC000000001 /* SampleCode.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = SampleCode.xcconfig; path = ../Configuration/SampleCode.xcconfig; sourceTree = ""; };
79 | D547BFA0D5465A8000000001 /* LICENSE.txt */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = LICENSE.txt; sourceTree = ""; };
80 | DE03695622AD7FC400C24B7C /* ConnectedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectedView.swift; sourceTree = ""; };
81 | DE03695822AD7FDC00C24B7C /* StoreConnector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreConnector.swift; sourceTree = ""; };
82 | DE03695A22AD9FDA00C24B7C /* Reducable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reducable.swift; sourceTree = ""; };
83 | DE57486B22AD57E50087D23E /* Reducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reducer.swift; sourceTree = ""; };
84 | DE57486D22AD592A0087D23E /* State.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = State.swift; sourceTree = ""; };
85 | DE8158E822AAFEB900AAC05E /* StoreProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreProvider.swift; sourceTree = ""; };
86 | DE8158EA22AAFF7F00AAC05E /* Store.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Store.swift; sourceTree = ""; };
87 | DE8158ED22AB00DD00AAC05E /* Action.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Action.swift; sourceTree = ""; };
88 | DE8158EF22AB00FB00AAC05E /* ToggleLandmarkFavorite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleLandmarkFavorite.swift; sourceTree = ""; };
89 | DE8158F122AB011B00AAC05E /* ToggleFavoritesOnly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleFavoritesOnly.swift; sourceTree = ""; };
90 | /* End PBXFileReference section */
91 |
92 | /* Begin PBXFrameworksBuildPhase section */
93 | B739485F229F194000C47603 /* Frameworks */ = {
94 | isa = PBXFrameworksBuildPhase;
95 | buildActionMask = 2147483647;
96 | files = (
97 | );
98 | runOnlyForDeploymentPostprocessing = 0;
99 | };
100 | /* End PBXFrameworksBuildPhase section */
101 |
102 | /* Begin PBXGroup section */
103 | B7394859229F194000C47603 = {
104 | isa = PBXGroup;
105 | children = (
106 | DE57486622AD54D30087D23E /* Redux */,
107 | C4E4AAA0C4E4035000000001 /* README.md */,
108 | B7394864229F194000C47603 /* Landmarks */,
109 | B7394863229F194000C47603 /* Products */,
110 | D54316E0D542E6A000000001 /* Configuration */,
111 | D547CC40D547206000000001 /* LICENSE */,
112 | );
113 | sourceTree = "";
114 | };
115 | B7394863229F194000C47603 /* Products */ = {
116 | isa = PBXGroup;
117 | children = (
118 | B7394862229F194000C47603 /* Landmarks.app */,
119 | );
120 | name = Products;
121 | sourceTree = "";
122 | };
123 | B7394864229F194000C47603 /* Landmarks */ = {
124 | isa = PBXGroup;
125 | children = (
126 | DE57486D22AD592A0087D23E /* State.swift */,
127 | B7394865229F194000C47603 /* AppDelegate.swift */,
128 | B7394867229F194000C47603 /* SceneDelegate.swift */,
129 | DE8158EC22AB00D300AAC05E /* Actions */,
130 | DE57486722AD55F70087D23E /* Views */,
131 | B739487E229F282200C47603 /* Models */,
132 | B739487D229F1C0100C47603 /* Supporting Views */,
133 | B7394883229F291A00C47603 /* Resources */,
134 | B739486B229F194200C47603 /* Assets.xcassets */,
135 | B7394870229F194200C47603 /* LaunchScreen.storyboard */,
136 | B7394873229F194200C47603 /* Info.plist */,
137 | B739486D229F194200C47603 /* Preview Content */,
138 | );
139 | path = Landmarks;
140 | sourceTree = "";
141 | };
142 | B739486D229F194200C47603 /* Preview Content */ = {
143 | isa = PBXGroup;
144 | children = (
145 | B739486E229F194200C47603 /* Preview Assets.xcassets */,
146 | );
147 | path = "Preview Content";
148 | sourceTree = "";
149 | };
150 | B739487D229F1C0100C47603 /* Supporting Views */ = {
151 | isa = PBXGroup;
152 | children = (
153 | B7394879229F1B3F00C47603 /* CircleImage.swift */,
154 | B739487B229F1B6800C47603 /* MapView.swift */,
155 | );
156 | path = "Supporting Views";
157 | sourceTree = "";
158 | };
159 | B739487E229F282200C47603 /* Models */ = {
160 | isa = PBXGroup;
161 | children = (
162 | B7394880229F28B900C47603 /* Data.swift */,
163 | B739487F229F28B900C47603 /* Landmark.swift */,
164 | B7D2AAC4229F4D7C0061E5F5 /* UserData.swift */,
165 | );
166 | path = Models;
167 | sourceTree = "";
168 | };
169 | B7394883229F291A00C47603 /* Resources */ = {
170 | isa = PBXGroup;
171 | children = (
172 | B739488E229F292E00C47603 /* landmarkData.json */,
173 | B7394890229F292F00C47603 /* chilkoottrail.jpg */,
174 | B739488F229F292F00C47603 /* chincoteague.jpg */,
175 | B739488A229F292E00C47603 /* hiddenlake.jpg */,
176 | B7394886229F292E00C47603 /* icybay.jpg */,
177 | B7394887229F292E00C47603 /* lakemcdonald.jpg */,
178 | B7394884229F292D00C47603 /* rainbowlake.jpg */,
179 | B739488D229F292E00C47603 /* silversalmoncreek.jpg */,
180 | B739488B229F292E00C47603 /* stmarylake.jpg */,
181 | B7394888229F292E00C47603 /* turtlerock.jpg */,
182 | B739488C229F292E00C47603 /* twinlake.jpg */,
183 | B7394889229F292E00C47603 /* umbagog.jpg */,
184 | B73948A2229F3E2200C47603 /* charleyrivers.jpg */,
185 | );
186 | path = Resources;
187 | sourceTree = "";
188 | };
189 | D54316E0D542E6A000000001 /* Configuration */ = {
190 | isa = PBXGroup;
191 | children = (
192 | D5436C00D5426EC000000001 /* SampleCode.xcconfig */,
193 | );
194 | name = Configuration;
195 | sourceTree = "";
196 | };
197 | D547CC40D547206000000001 /* LICENSE */ = {
198 | isa = PBXGroup;
199 | children = (
200 | D547BFA0D5465A8000000001 /* LICENSE.txt */,
201 | );
202 | name = LICENSE;
203 | path = ../LICENSE;
204 | sourceTree = "";
205 | };
206 | DE57486622AD54D30087D23E /* Redux */ = {
207 | isa = PBXGroup;
208 | children = (
209 | DE8158ED22AB00DD00AAC05E /* Action.swift */,
210 | DE57486B22AD57E50087D23E /* Reducer.swift */,
211 | DE03695A22AD9FDA00C24B7C /* Reducable.swift */,
212 | DE8158EA22AAFF7F00AAC05E /* Store.swift */,
213 | DE8158E822AAFEB900AAC05E /* StoreProvider.swift */,
214 | DE03695622AD7FC400C24B7C /* ConnectedView.swift */,
215 | DE03695822AD7FDC00C24B7C /* StoreConnector.swift */,
216 | );
217 | path = Redux;
218 | sourceTree = "";
219 | };
220 | DE57486722AD55F70087D23E /* Views */ = {
221 | isa = PBXGroup;
222 | children = (
223 | B73948A0229F2E1F00C47603 /* LandmarkList.swift */,
224 | B739489E229F2D9700C47603 /* LandmarkRow.swift */,
225 | B7394869229F194000C47603 /* LandmarkDetail.swift */,
226 | );
227 | path = Views;
228 | sourceTree = "";
229 | };
230 | DE8158EC22AB00D300AAC05E /* Actions */ = {
231 | isa = PBXGroup;
232 | children = (
233 | DE8158EF22AB00FB00AAC05E /* ToggleLandmarkFavorite.swift */,
234 | DE8158F122AB011B00AAC05E /* ToggleFavoritesOnly.swift */,
235 | );
236 | path = Actions;
237 | sourceTree = "";
238 | };
239 | /* End PBXGroup section */
240 |
241 | /* Begin PBXNativeTarget section */
242 | B7394861229F194000C47603 /* Landmarks */ = {
243 | isa = PBXNativeTarget;
244 | buildConfigurationList = B7394876229F194200C47603 /* Build configuration list for PBXNativeTarget "Landmarks" */;
245 | buildPhases = (
246 | B739485E229F194000C47603 /* Sources */,
247 | B739485F229F194000C47603 /* Frameworks */,
248 | B7394860229F194000C47603 /* Resources */,
249 | );
250 | buildRules = (
251 | );
252 | dependencies = (
253 | );
254 | name = Landmarks;
255 | productName = Landmarks;
256 | productReference = B7394862229F194000C47603 /* Landmarks.app */;
257 | productType = "com.apple.product-type.application";
258 | };
259 | /* End PBXNativeTarget section */
260 |
261 | /* Begin PBXProject section */
262 | B739485A229F194000C47603 /* Project object */ = {
263 | isa = PBXProject;
264 | attributes = {
265 | LastSwiftUpdateCheck = 1100;
266 | LastUpgradeCheck = 1100;
267 | ORGANIZATIONNAME = Apple;
268 | TargetAttributes = {
269 | B7394861229F194000C47603 = {
270 | CreatedOnToolsVersion = 11.0;
271 | };
272 | };
273 | };
274 | buildConfigurationList = B739485D229F194000C47603 /* Build configuration list for PBXProject "HandlingUserInput" */;
275 | compatibilityVersion = "Xcode 9.3";
276 | developmentRegion = en;
277 | hasScannedForEncodings = 0;
278 | knownRegions = (
279 | en,
280 | Base,
281 | );
282 | mainGroup = B7394859229F194000C47603;
283 | productRefGroup = B7394863229F194000C47603 /* Products */;
284 | projectDirPath = "";
285 | projectRoot = "";
286 | targets = (
287 | B7394861229F194000C47603 /* Landmarks */,
288 | );
289 | };
290 | /* End PBXProject section */
291 |
292 | /* Begin PBXResourcesBuildPhase section */
293 | B7394860229F194000C47603 /* Resources */ = {
294 | isa = PBXResourcesBuildPhase;
295 | buildActionMask = 2147483647;
296 | files = (
297 | B739489A229F292F00C47603 /* silversalmoncreek.jpg in Resources */,
298 | B7394894229F292F00C47603 /* lakemcdonald.jpg in Resources */,
299 | B7394872229F194200C47603 /* LaunchScreen.storyboard in Resources */,
300 | B73948A3229F3E2200C47603 /* charleyrivers.jpg in Resources */,
301 | B7394891229F292F00C47603 /* rainbowlake.jpg in Resources */,
302 | B739486F229F194200C47603 /* Preview Assets.xcassets in Resources */,
303 | B7394895229F292F00C47603 /* turtlerock.jpg in Resources */,
304 | B739486C229F194200C47603 /* Assets.xcassets in Resources */,
305 | B7394899229F292F00C47603 /* twinlake.jpg in Resources */,
306 | B7394896229F292F00C47603 /* umbagog.jpg in Resources */,
307 | B739489D229F292F00C47603 /* chilkoottrail.jpg in Resources */,
308 | B739489C229F292F00C47603 /* chincoteague.jpg in Resources */,
309 | B739489B229F292F00C47603 /* landmarkData.json in Resources */,
310 | B7394893229F292F00C47603 /* icybay.jpg in Resources */,
311 | B7394897229F292F00C47603 /* hiddenlake.jpg in Resources */,
312 | B7394898229F292F00C47603 /* stmarylake.jpg in Resources */,
313 | );
314 | runOnlyForDeploymentPostprocessing = 0;
315 | };
316 | /* End PBXResourcesBuildPhase section */
317 |
318 | /* Begin PBXSourcesBuildPhase section */
319 | B739485E229F194000C47603 /* Sources */ = {
320 | isa = PBXSourcesBuildPhase;
321 | buildActionMask = 2147483647;
322 | files = (
323 | B7394882229F28B900C47603 /* Data.swift in Sources */,
324 | B7394866229F194000C47603 /* AppDelegate.swift in Sources */,
325 | DE03695922AD7FDC00C24B7C /* StoreConnector.swift in Sources */,
326 | DE8158F222AB011B00AAC05E /* ToggleFavoritesOnly.swift in Sources */,
327 | B739487A229F1B3F00C47603 /* CircleImage.swift in Sources */,
328 | DE03695B22AD9FDA00C24B7C /* Reducable.swift in Sources */,
329 | DE57486E22AD592A0087D23E /* State.swift in Sources */,
330 | DE8158F022AB00FB00AAC05E /* ToggleLandmarkFavorite.swift in Sources */,
331 | DE8158EB22AAFF7F00AAC05E /* Store.swift in Sources */,
332 | B739487C229F1B6800C47603 /* MapView.swift in Sources */,
333 | DE03695722AD7FC400C24B7C /* ConnectedView.swift in Sources */,
334 | B73948A1229F2E1F00C47603 /* LandmarkList.swift in Sources */,
335 | DE57486C22AD57E50087D23E /* Reducer.swift in Sources */,
336 | DE8158E922AAFEB900AAC05E /* StoreProvider.swift in Sources */,
337 | B7394868229F194000C47603 /* SceneDelegate.swift in Sources */,
338 | B739486A229F194000C47603 /* LandmarkDetail.swift in Sources */,
339 | B739489F229F2D9700C47603 /* LandmarkRow.swift in Sources */,
340 | DE8158EE22AB00DD00AAC05E /* Action.swift in Sources */,
341 | B7D2AAC5229F4D7C0061E5F5 /* UserData.swift in Sources */,
342 | B7394881229F28B900C47603 /* Landmark.swift in Sources */,
343 | );
344 | runOnlyForDeploymentPostprocessing = 0;
345 | };
346 | /* End PBXSourcesBuildPhase section */
347 |
348 | /* Begin PBXVariantGroup section */
349 | B7394870229F194200C47603 /* LaunchScreen.storyboard */ = {
350 | isa = PBXVariantGroup;
351 | children = (
352 | B7394871229F194200C47603 /* Base */,
353 | );
354 | name = LaunchScreen.storyboard;
355 | sourceTree = "";
356 | };
357 | /* End PBXVariantGroup section */
358 |
359 | /* Begin XCBuildConfiguration section */
360 | B7394874229F194200C47603 /* Debug */ = {
361 | isa = XCBuildConfiguration;
362 | baseConfigurationReference = D5436C00D5426EC000000001 /* SampleCode.xcconfig */;
363 | buildSettings = {
364 | ALWAYS_SEARCH_USER_PATHS = NO;
365 | CLANG_ANALYZER_NONNULL = YES;
366 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
367 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
368 | CLANG_CXX_LIBRARY = "libc++";
369 | CLANG_ENABLE_MODULES = YES;
370 | CLANG_ENABLE_OBJC_ARC = YES;
371 | CLANG_ENABLE_OBJC_WEAK = YES;
372 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
373 | CLANG_WARN_BOOL_CONVERSION = YES;
374 | CLANG_WARN_COMMA = YES;
375 | CLANG_WARN_CONSTANT_CONVERSION = YES;
376 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
377 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
378 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
379 | CLANG_WARN_EMPTY_BODY = YES;
380 | CLANG_WARN_ENUM_CONVERSION = YES;
381 | CLANG_WARN_INFINITE_RECURSION = YES;
382 | CLANG_WARN_INT_CONVERSION = YES;
383 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
384 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
385 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
386 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
387 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
388 | CLANG_WARN_STRICT_PROTOTYPES = YES;
389 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
390 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
391 | CLANG_WARN_UNREACHABLE_CODE = YES;
392 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
393 | COPY_PHASE_STRIP = NO;
394 | DEBUG_INFORMATION_FORMAT = dwarf;
395 | ENABLE_STRICT_OBJC_MSGSEND = YES;
396 | ENABLE_TESTABILITY = YES;
397 | GCC_C_LANGUAGE_STANDARD = gnu11;
398 | GCC_DYNAMIC_NO_PIC = NO;
399 | GCC_NO_COMMON_BLOCKS = YES;
400 | GCC_OPTIMIZATION_LEVEL = 0;
401 | GCC_PREPROCESSOR_DEFINITIONS = (
402 | "DEBUG=1",
403 | "$(inherited)",
404 | );
405 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
406 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
407 | GCC_WARN_UNDECLARED_SELECTOR = YES;
408 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
409 | GCC_WARN_UNUSED_FUNCTION = YES;
410 | GCC_WARN_UNUSED_VARIABLE = YES;
411 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
412 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
413 | MTL_FAST_MATH = YES;
414 | ONLY_ACTIVE_ARCH = YES;
415 | SDKROOT = iphoneos;
416 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
417 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
418 | };
419 | name = Debug;
420 | };
421 | B7394875229F194200C47603 /* Release */ = {
422 | isa = XCBuildConfiguration;
423 | baseConfigurationReference = D5436C00D5426EC000000001 /* SampleCode.xcconfig */;
424 | buildSettings = {
425 | ALWAYS_SEARCH_USER_PATHS = NO;
426 | CLANG_ANALYZER_NONNULL = YES;
427 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
428 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
429 | CLANG_CXX_LIBRARY = "libc++";
430 | CLANG_ENABLE_MODULES = YES;
431 | CLANG_ENABLE_OBJC_ARC = YES;
432 | CLANG_ENABLE_OBJC_WEAK = YES;
433 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
434 | CLANG_WARN_BOOL_CONVERSION = YES;
435 | CLANG_WARN_COMMA = YES;
436 | CLANG_WARN_CONSTANT_CONVERSION = YES;
437 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
438 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
439 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
440 | CLANG_WARN_EMPTY_BODY = YES;
441 | CLANG_WARN_ENUM_CONVERSION = YES;
442 | CLANG_WARN_INFINITE_RECURSION = YES;
443 | CLANG_WARN_INT_CONVERSION = YES;
444 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
445 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
446 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
447 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
448 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
449 | CLANG_WARN_STRICT_PROTOTYPES = YES;
450 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
451 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
452 | CLANG_WARN_UNREACHABLE_CODE = YES;
453 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
454 | COPY_PHASE_STRIP = NO;
455 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
456 | ENABLE_NS_ASSERTIONS = NO;
457 | ENABLE_STRICT_OBJC_MSGSEND = YES;
458 | GCC_C_LANGUAGE_STANDARD = gnu11;
459 | GCC_NO_COMMON_BLOCKS = YES;
460 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
461 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
462 | GCC_WARN_UNDECLARED_SELECTOR = YES;
463 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
464 | GCC_WARN_UNUSED_FUNCTION = YES;
465 | GCC_WARN_UNUSED_VARIABLE = YES;
466 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
467 | MTL_ENABLE_DEBUG_INFO = NO;
468 | MTL_FAST_MATH = YES;
469 | SDKROOT = iphoneos;
470 | SWIFT_COMPILATION_MODE = wholemodule;
471 | SWIFT_OPTIMIZATION_LEVEL = "-O";
472 | VALIDATE_PRODUCT = YES;
473 | };
474 | name = Release;
475 | };
476 | B7394877229F194200C47603 /* Debug */ = {
477 | isa = XCBuildConfiguration;
478 | baseConfigurationReference = D5436C00D5426EC000000001 /* SampleCode.xcconfig */;
479 | buildSettings = {
480 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
481 | CODE_SIGN_STYLE = Automatic;
482 | DEVELOPMENT_ASSET_PATHS = "Landmarks/Preview\\ Content";
483 | DEVELOPMENT_TEAM = "";
484 | ENABLE_PREVIEWS = YES;
485 | INFOPLIST_FILE = Landmarks/Info.plist;
486 | LD_RUNPATH_SEARCH_PATHS = (
487 | "$(inherited)",
488 | "@executable_path/Frameworks",
489 | );
490 | PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.Landmarks${SAMPLE_CODE_DISAMBIGUATOR}";
491 | PRODUCT_NAME = "$(TARGET_NAME)";
492 | PROVISIONING_PROFILE_SPECIFIER = "";
493 | SWIFT_VERSION = 5.0;
494 | TARGETED_DEVICE_FAMILY = "1,2";
495 | };
496 | name = Debug;
497 | };
498 | B7394878229F194200C47603 /* Release */ = {
499 | isa = XCBuildConfiguration;
500 | baseConfigurationReference = D5436C00D5426EC000000001 /* SampleCode.xcconfig */;
501 | buildSettings = {
502 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
503 | CODE_SIGN_STYLE = Automatic;
504 | DEVELOPMENT_ASSET_PATHS = "Landmarks/Preview\\ Content";
505 | DEVELOPMENT_TEAM = "";
506 | ENABLE_PREVIEWS = YES;
507 | INFOPLIST_FILE = Landmarks/Info.plist;
508 | LD_RUNPATH_SEARCH_PATHS = (
509 | "$(inherited)",
510 | "@executable_path/Frameworks",
511 | );
512 | PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.Landmarks${SAMPLE_CODE_DISAMBIGUATOR}";
513 | PRODUCT_NAME = "$(TARGET_NAME)";
514 | PROVISIONING_PROFILE_SPECIFIER = "";
515 | SWIFT_VERSION = 5.0;
516 | TARGETED_DEVICE_FAMILY = "1,2";
517 | };
518 | name = Release;
519 | };
520 | /* End XCBuildConfiguration section */
521 |
522 | /* Begin XCConfigurationList section */
523 | B739485D229F194000C47603 /* Build configuration list for PBXProject "HandlingUserInput" */ = {
524 | isa = XCConfigurationList;
525 | buildConfigurations = (
526 | B7394874229F194200C47603 /* Debug */,
527 | B7394875229F194200C47603 /* Release */,
528 | );
529 | defaultConfigurationIsVisible = 0;
530 | defaultConfigurationName = Release;
531 | };
532 | B7394876229F194200C47603 /* Build configuration list for PBXNativeTarget "Landmarks" */ = {
533 | isa = XCConfigurationList;
534 | buildConfigurations = (
535 | B7394877229F194200C47603 /* Debug */,
536 | B7394878229F194200C47603 /* Release */,
537 | );
538 | defaultConfigurationIsVisible = 0;
539 | defaultConfigurationName = Release;
540 | };
541 | /* End XCConfigurationList section */
542 | };
543 | rootObject = B739485A229F194000C47603 /* Project object */;
544 | }
545 |
--------------------------------------------------------------------------------
/HandlingUserInput.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/HandlingUserInput.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | BuildSystemType
6 | Latest
7 |
8 |
9 |
--------------------------------------------------------------------------------
/HandlingUserInput.xcodeproj/xcuserdata/ademedetskii.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
9 |
21 |
22 |
23 |
25 |
37 |
38 |
39 |
41 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/HandlingUserInput.xcodeproj/xcuserdata/ademedetskii.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | Landmarks.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/Landmarks/Actions/ToggleFavoritesOnly.swift:
--------------------------------------------------------------------------------
1 | extension Actions {
2 | struct ToggleFavoritesOnly: Action {
3 | let shouldShowFavorites: Bool
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/Landmarks/Actions/ToggleLandmarkFavorite.swift:
--------------------------------------------------------------------------------
1 | extension Actions {
2 | struct ToggleLandmarkFavorite: Action {
3 | let id: Landmark.ID
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/Landmarks/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | /*
2 | See LICENSE folder for this sample’s licensing information.
3 |
4 | Abstract:
5 | The application delegate.
6 | */
7 |
8 | import UIKit
9 |
10 | @UIApplicationMain
11 | class AppDelegate: UIResponder, UIApplicationDelegate {
12 |
13 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
14 | // Override point for customization after application launch.
15 | return true
16 | }
17 |
18 | func applicationWillTerminate(_ application: UIApplication) {
19 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
20 | }
21 |
22 | // MARK: UISceneSession Lifecycle
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 |
--------------------------------------------------------------------------------
/Landmarks/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "landmark_app_icon_40x40.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "idiom" : "iphone",
11 | "size" : "20x20",
12 | "scale" : "3x"
13 | },
14 | {
15 | "size" : "29x29",
16 | "idiom" : "iphone",
17 | "filename" : "landmark_app_icon_58x58.png",
18 | "scale" : "2x"
19 | },
20 | {
21 | "size" : "29x29",
22 | "idiom" : "iphone",
23 | "filename" : "landmark_app_icon_87x87.png",
24 | "scale" : "3x"
25 | },
26 | {
27 | "size" : "40x40",
28 | "idiom" : "iphone",
29 | "filename" : "landmark_app_icon_80x80.png",
30 | "scale" : "2x"
31 | },
32 | {
33 | "size" : "40x40",
34 | "idiom" : "iphone",
35 | "filename" : "landmark_app_icon_120x120.png",
36 | "scale" : "3x"
37 | },
38 | {
39 | "size" : "60x60",
40 | "idiom" : "iphone",
41 | "filename" : "landmark_app_icon_120x120-1.png",
42 | "scale" : "2x"
43 | },
44 | {
45 | "size" : "60x60",
46 | "idiom" : "iphone",
47 | "filename" : "landmark_app_icon_180x180.png",
48 | "scale" : "3x"
49 | },
50 | {
51 | "idiom" : "ipad",
52 | "size" : "20x20",
53 | "scale" : "1x"
54 | },
55 | {
56 | "size" : "20x20",
57 | "idiom" : "ipad",
58 | "filename" : "landmark_app_icon_40x40-1.png",
59 | "scale" : "2x"
60 | },
61 | {
62 | "idiom" : "ipad",
63 | "size" : "29x29",
64 | "scale" : "1x"
65 | },
66 | {
67 | "size" : "29x29",
68 | "idiom" : "ipad",
69 | "filename" : "landmark_app_icon_58x58-1.png",
70 | "scale" : "2x"
71 | },
72 | {
73 | "size" : "40x40",
74 | "idiom" : "ipad",
75 | "filename" : "landmark_app_icon_40x40-2.png",
76 | "scale" : "1x"
77 | },
78 | {
79 | "size" : "40x40",
80 | "idiom" : "ipad",
81 | "filename" : "landmark_app_icon_80x80-1.png",
82 | "scale" : "2x"
83 | },
84 | {
85 | "size" : "76x76",
86 | "idiom" : "ipad",
87 | "filename" : "landmark_app_icon_76x76.png",
88 | "scale" : "1x"
89 | },
90 | {
91 | "size" : "76x76",
92 | "idiom" : "ipad",
93 | "filename" : "landmark_app_icon_152x152.png",
94 | "scale" : "2x"
95 | },
96 | {
97 | "size" : "83.5x83.5",
98 | "idiom" : "ipad",
99 | "filename" : "landmark_app_icon_167x167.png",
100 | "scale" : "2x"
101 | },
102 | {
103 | "size" : "1024x1024",
104 | "idiom" : "ios-marketing",
105 | "filename" : "landmark_app_icon_1024x1024.png",
106 | "scale" : "1x"
107 | }
108 | ],
109 | "info" : {
110 | "version" : 1,
111 | "author" : "xcode"
112 | }
113 | }
--------------------------------------------------------------------------------
/Landmarks/Assets.xcassets/AppIcon.appiconset/landmark_app_icon_1024x1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksii-demedetskyi/HandlingUserInput/6047294e9fc6c5dcc7538eb4b7d3fae925d59c6b/Landmarks/Assets.xcassets/AppIcon.appiconset/landmark_app_icon_1024x1024.png
--------------------------------------------------------------------------------
/Landmarks/Assets.xcassets/AppIcon.appiconset/landmark_app_icon_120x120-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksii-demedetskyi/HandlingUserInput/6047294e9fc6c5dcc7538eb4b7d3fae925d59c6b/Landmarks/Assets.xcassets/AppIcon.appiconset/landmark_app_icon_120x120-1.png
--------------------------------------------------------------------------------
/Landmarks/Assets.xcassets/AppIcon.appiconset/landmark_app_icon_120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksii-demedetskyi/HandlingUserInput/6047294e9fc6c5dcc7538eb4b7d3fae925d59c6b/Landmarks/Assets.xcassets/AppIcon.appiconset/landmark_app_icon_120x120.png
--------------------------------------------------------------------------------
/Landmarks/Assets.xcassets/AppIcon.appiconset/landmark_app_icon_152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksii-demedetskyi/HandlingUserInput/6047294e9fc6c5dcc7538eb4b7d3fae925d59c6b/Landmarks/Assets.xcassets/AppIcon.appiconset/landmark_app_icon_152x152.png
--------------------------------------------------------------------------------
/Landmarks/Assets.xcassets/AppIcon.appiconset/landmark_app_icon_167x167.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksii-demedetskyi/HandlingUserInput/6047294e9fc6c5dcc7538eb4b7d3fae925d59c6b/Landmarks/Assets.xcassets/AppIcon.appiconset/landmark_app_icon_167x167.png
--------------------------------------------------------------------------------
/Landmarks/Assets.xcassets/AppIcon.appiconset/landmark_app_icon_180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksii-demedetskyi/HandlingUserInput/6047294e9fc6c5dcc7538eb4b7d3fae925d59c6b/Landmarks/Assets.xcassets/AppIcon.appiconset/landmark_app_icon_180x180.png
--------------------------------------------------------------------------------
/Landmarks/Assets.xcassets/AppIcon.appiconset/landmark_app_icon_40x40-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksii-demedetskyi/HandlingUserInput/6047294e9fc6c5dcc7538eb4b7d3fae925d59c6b/Landmarks/Assets.xcassets/AppIcon.appiconset/landmark_app_icon_40x40-1.png
--------------------------------------------------------------------------------
/Landmarks/Assets.xcassets/AppIcon.appiconset/landmark_app_icon_40x40-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksii-demedetskyi/HandlingUserInput/6047294e9fc6c5dcc7538eb4b7d3fae925d59c6b/Landmarks/Assets.xcassets/AppIcon.appiconset/landmark_app_icon_40x40-2.png
--------------------------------------------------------------------------------
/Landmarks/Assets.xcassets/AppIcon.appiconset/landmark_app_icon_40x40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksii-demedetskyi/HandlingUserInput/6047294e9fc6c5dcc7538eb4b7d3fae925d59c6b/Landmarks/Assets.xcassets/AppIcon.appiconset/landmark_app_icon_40x40.png
--------------------------------------------------------------------------------
/Landmarks/Assets.xcassets/AppIcon.appiconset/landmark_app_icon_58x58-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksii-demedetskyi/HandlingUserInput/6047294e9fc6c5dcc7538eb4b7d3fae925d59c6b/Landmarks/Assets.xcassets/AppIcon.appiconset/landmark_app_icon_58x58-1.png
--------------------------------------------------------------------------------
/Landmarks/Assets.xcassets/AppIcon.appiconset/landmark_app_icon_58x58.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksii-demedetskyi/HandlingUserInput/6047294e9fc6c5dcc7538eb4b7d3fae925d59c6b/Landmarks/Assets.xcassets/AppIcon.appiconset/landmark_app_icon_58x58.png
--------------------------------------------------------------------------------
/Landmarks/Assets.xcassets/AppIcon.appiconset/landmark_app_icon_76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksii-demedetskyi/HandlingUserInput/6047294e9fc6c5dcc7538eb4b7d3fae925d59c6b/Landmarks/Assets.xcassets/AppIcon.appiconset/landmark_app_icon_76x76.png
--------------------------------------------------------------------------------
/Landmarks/Assets.xcassets/AppIcon.appiconset/landmark_app_icon_80x80-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksii-demedetskyi/HandlingUserInput/6047294e9fc6c5dcc7538eb4b7d3fae925d59c6b/Landmarks/Assets.xcassets/AppIcon.appiconset/landmark_app_icon_80x80-1.png
--------------------------------------------------------------------------------
/Landmarks/Assets.xcassets/AppIcon.appiconset/landmark_app_icon_80x80.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksii-demedetskyi/HandlingUserInput/6047294e9fc6c5dcc7538eb4b7d3fae925d59c6b/Landmarks/Assets.xcassets/AppIcon.appiconset/landmark_app_icon_80x80.png
--------------------------------------------------------------------------------
/Landmarks/Assets.xcassets/AppIcon.appiconset/landmark_app_icon_87x87.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksii-demedetskyi/HandlingUserInput/6047294e9fc6c5dcc7538eb4b7d3fae925d59c6b/Landmarks/Assets.xcassets/AppIcon.appiconset/landmark_app_icon_87x87.png
--------------------------------------------------------------------------------
/Landmarks/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Landmarks/Assets.xcassets/turtlerock.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "turtlerock.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 | }
--------------------------------------------------------------------------------
/Landmarks/Assets.xcassets/turtlerock.imageset/turtlerock.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksii-demedetskyi/HandlingUserInput/6047294e9fc6c5dcc7538eb4b7d3fae925d59c6b/Landmarks/Assets.xcassets/turtlerock.imageset/turtlerock.jpg
--------------------------------------------------------------------------------
/Landmarks/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 |
--------------------------------------------------------------------------------
/Landmarks/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIApplicationSceneManifest
24 |
25 | UIApplicationSupportsMultipleScenes
26 |
27 | UISceneConfigurations
28 |
29 | UIWindowSceneSessionRoleApplication
30 |
31 |
32 | UILaunchStoryboardName
33 | LaunchScreen
34 | UISceneConfigurationName
35 | Default Configuration
36 | UISceneDelegateClassName
37 | $(PRODUCT_MODULE_NAME).SceneDelegate
38 |
39 |
40 |
41 |
42 | UILaunchStoryboardName
43 | LaunchScreen
44 | UIRequiredDeviceCapabilities
45 |
46 | armv7
47 |
48 | UISupportedInterfaceOrientations
49 |
50 | UIInterfaceOrientationPortrait
51 | UIInterfaceOrientationLandscapeLeft
52 | UIInterfaceOrientationLandscapeRight
53 |
54 | UISupportedInterfaceOrientations~ipad
55 |
56 | UIInterfaceOrientationPortrait
57 | UIInterfaceOrientationPortraitUpsideDown
58 | UIInterfaceOrientationLandscapeLeft
59 | UIInterfaceOrientationLandscapeRight
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/Landmarks/Models/Data.swift:
--------------------------------------------------------------------------------
1 | /*
2 | See LICENSE folder for this sample’s licensing information.
3 |
4 | Abstract:
5 | Helpers for loading images and data.
6 | */
7 |
8 | import UIKit
9 | import SwiftUI
10 | import CoreLocation
11 |
12 | let landmarkData: [Landmark] = load("landmarkData.json")
13 |
14 | func load(_ filename: String, as type: T.Type = T.self) -> T {
15 | let data: Data
16 |
17 | guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
18 | else {
19 | fatalError("Couldn't find \(filename) in main bundle.")
20 | }
21 |
22 | do {
23 | data = try Data(contentsOf: file)
24 | } catch {
25 | fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
26 | }
27 |
28 | do {
29 | let decoder = JSONDecoder()
30 | return try decoder.decode(T.self, from: data)
31 | } catch {
32 | fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
33 | }
34 | }
35 |
36 | final class ImageStore {
37 | fileprivate typealias _ImageDictionary = [String: [Int: CGImage]]
38 | fileprivate var images: _ImageDictionary = [:]
39 |
40 | fileprivate static var originalSize = 250
41 | fileprivate static var scale = 2
42 |
43 | static var shared = ImageStore()
44 |
45 | func image(name: String, size: Int) -> Image {
46 | let index = _guaranteeInitialImage(name: name)
47 |
48 | let sizedImage = images.values[index][size]
49 | ?? _sizeImage(images.values[index][ImageStore.originalSize]!, to: size * ImageStore.scale)
50 | images.values[index][size] = sizedImage
51 |
52 | return Image(sizedImage, scale: Length(ImageStore.scale), label: Text(verbatim: name))
53 | }
54 |
55 | fileprivate func _guaranteeInitialImage(name: String) -> _ImageDictionary.Index {
56 | if let index = images.index(forKey: name) { return index }
57 |
58 | guard
59 | let url = Bundle.main.url(forResource: name, withExtension: "jpg"),
60 | let imageSource = CGImageSourceCreateWithURL(url as NSURL, nil),
61 | let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil)
62 | else {
63 | fatalError("Couldn't load image \(name).jpg from main bundle.")
64 | }
65 |
66 | images[name] = [ImageStore.originalSize: image]
67 | return images.index(forKey: name)!
68 | }
69 |
70 | fileprivate func _sizeImage(_ image: CGImage, to size: Int) -> CGImage {
71 | guard
72 | let colorSpace = image.colorSpace,
73 | let context = CGContext(
74 | data: nil,
75 | width: size, height: size,
76 | bitsPerComponent: image.bitsPerComponent,
77 | bytesPerRow: image.bytesPerRow,
78 | space: colorSpace,
79 | bitmapInfo: image.bitmapInfo.rawValue)
80 | else {
81 | fatalError("Couldn't create graphics context.")
82 | }
83 | context.interpolationQuality = .high
84 | context.draw(image, in: CGRect(x: 0, y: 0, width: size, height: size))
85 |
86 | if let sizedImage = context.makeImage() {
87 | return sizedImage
88 | } else {
89 | fatalError("Couldn't resize image.")
90 | }
91 | }
92 | }
93 |
94 |
--------------------------------------------------------------------------------
/Landmarks/Models/Landmark.swift:
--------------------------------------------------------------------------------
1 | /*
2 | See LICENSE folder for this sample’s licensing information.
3 |
4 | Abstract:
5 | The model for an individual landmark.
6 | */
7 |
8 | import SwiftUI
9 | import CoreLocation
10 |
11 | struct Landmark: Hashable, Codable, Identifiable {
12 | var id: Int
13 | var name: String
14 | var imageName: String
15 | fileprivate var coordinates: Coordinates
16 | var state: String
17 | var park: String
18 | var category: Category
19 | var isFavorite: Bool
20 |
21 | var locationCoordinate: CLLocationCoordinate2D {
22 | CLLocationCoordinate2D(
23 | latitude: coordinates.latitude,
24 | longitude: coordinates.longitude)
25 | }
26 |
27 | func image(forSize size: Int) -> Image {
28 | ImageStore.shared.image(name: imageName, size: size)
29 | }
30 |
31 | enum Category: String, CaseIterable, Codable, Hashable {
32 | case featured = "Featured"
33 | case lakes = "Lakes"
34 | case rivers = "Rivers"
35 | case mountains = "Mountains"
36 | }
37 | }
38 |
39 | struct Coordinates: Hashable, Codable {
40 | var latitude: Double
41 | var longitude: Double
42 | }
43 |
--------------------------------------------------------------------------------
/Landmarks/Models/UserData.swift:
--------------------------------------------------------------------------------
1 | /*
2 | See LICENSE folder for this sample’s licensing information.
3 |
4 | Abstract:
5 | A model object that stores app data.
6 | */
7 |
8 | import Combine
9 | import SwiftUI
10 |
11 | final class UserData: BindableObject {
12 | let didChange = PassthroughSubject()
13 |
14 | private(set) var showFavoritesOnly = false {
15 | didSet {
16 | didChange.send(self)
17 | }
18 | }
19 |
20 | private(set) var landmarks = landmarkData {
21 | didSet {
22 | didChange.send(self)
23 | }
24 | }
25 |
26 | func reduce(action: Action) {
27 | switch action {
28 |
29 | case let action as Actions.ToggleLandmarkFavorite:
30 | let maybeIndex = landmarks.firstIndex { $0.id == action.id }
31 | guard let index = maybeIndex else {
32 | break;
33 | }
34 |
35 | landmarks[index].isFavorite.toggle()
36 |
37 | case let action as Actions.ToggleFavoritesOnly:
38 | showFavoritesOnly = action.shouldShowFavorites
39 | default: break }
40 | }
41 |
42 | func bind(_ action: Action) -> () -> Void {
43 | return {
44 | self.reduce(action: action)
45 | }
46 | }
47 |
48 | func binding(of valuePath: KeyPath, to action: @escaping (T) -> Action) -> Binding {
49 | return Binding(
50 | getValue: { self[keyPath: valuePath] },
51 | setValue: { self.reduce(action: action($0)) }
52 | )
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/Landmarks/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Landmarks/Resources/charleyrivers.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksii-demedetskyi/HandlingUserInput/6047294e9fc6c5dcc7538eb4b7d3fae925d59c6b/Landmarks/Resources/charleyrivers.jpg
--------------------------------------------------------------------------------
/Landmarks/Resources/chilkoottrail.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksii-demedetskyi/HandlingUserInput/6047294e9fc6c5dcc7538eb4b7d3fae925d59c6b/Landmarks/Resources/chilkoottrail.jpg
--------------------------------------------------------------------------------
/Landmarks/Resources/chincoteague.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksii-demedetskyi/HandlingUserInput/6047294e9fc6c5dcc7538eb4b7d3fae925d59c6b/Landmarks/Resources/chincoteague.jpg
--------------------------------------------------------------------------------
/Landmarks/Resources/hiddenlake.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksii-demedetskyi/HandlingUserInput/6047294e9fc6c5dcc7538eb4b7d3fae925d59c6b/Landmarks/Resources/hiddenlake.jpg
--------------------------------------------------------------------------------
/Landmarks/Resources/icybay.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksii-demedetskyi/HandlingUserInput/6047294e9fc6c5dcc7538eb4b7d3fae925d59c6b/Landmarks/Resources/icybay.jpg
--------------------------------------------------------------------------------
/Landmarks/Resources/lakemcdonald.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksii-demedetskyi/HandlingUserInput/6047294e9fc6c5dcc7538eb4b7d3fae925d59c6b/Landmarks/Resources/lakemcdonald.jpg
--------------------------------------------------------------------------------
/Landmarks/Resources/landmarkData.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "Turtle Rock",
4 | "category": "Rivers",
5 | "city": "Twentynine Palms",
6 | "state": "California",
7 | "id": 1001,
8 | "isFeatured": true,
9 | "isFavorite": true,
10 | "park": "Joshua Tree National Park",
11 | "coordinates": {
12 | "longitude": -116.166868,
13 | "latitude": 34.011286
14 | },
15 | "imageName": "turtlerock"
16 | },
17 | {
18 | "name": "Silver Salmon Creek",
19 | "category": "Lakes",
20 | "city": "Port Alsworth",
21 | "state": "Alaska",
22 | "id": 1002,
23 | "isFeatured": false,
24 | "isFavorite": false,
25 | "park": "Lake Clark National Park and Preserve",
26 | "coordinates": {
27 | "longitude": -152.665167,
28 | "latitude": 59.980167
29 | },
30 | "imageName": "silversalmoncreek"
31 | },
32 | {
33 | "name": "Chilkoot Trail",
34 | "category": "Mountains",
35 | "city": "Skagway",
36 | "state": "Alaska",
37 | "id": 1003,
38 | "isFeatured": false,
39 | "isFavorite": true,
40 | "park": "Klondike Gold Rush National Historical Park",
41 | "coordinates": {
42 | "longitude": -135.334571,
43 | "latitude": 59.560551
44 | },
45 | "imageName": "chilkoottrail"
46 | },
47 | {
48 | "name": "St. Mary Lake",
49 | "category": "Lakes",
50 | "city": "Browning",
51 | "state": "Montana",
52 | "id": 1004,
53 | "isFeatured": true,
54 | "isFavorite": true,
55 | "park": "Glacier National Park",
56 | "coordinates": {
57 | "longitude": -113.536248,
58 | "latitude": 48.69423
59 | },
60 | "imageName": "stmarylake"
61 | },
62 | {
63 | "name": "Twin Lake",
64 | "category": "Lakes",
65 | "city": "Twin Lakes",
66 | "state": "Alaska",
67 | "id": 1005,
68 | "isFeatured": false,
69 | "isFavorite": false,
70 | "park": "Lake Clark National Park and Preserve",
71 | "coordinates": {
72 | "longitude": -153.849883,
73 | "latitude": 60.641684
74 | },
75 | "imageName": "twinlake"
76 | },
77 | {
78 | "name": "Lake McDonald",
79 | "category": "Mountains",
80 | "city": "West Glacier",
81 | "state": "Montana",
82 | "id": 1006,
83 | "isFeatured": false,
84 | "isFavorite": false,
85 | "park": "Glacier National Park",
86 | "coordinates": {
87 | "longitude": -113.934831,
88 | "latitude": 48.56002
89 | },
90 | "imageName": "lakemcdonald"
91 | },
92 | {
93 | "name": "Charley Rivers",
94 | "category": "Rivers",
95 | "city": "Eaking",
96 | "state": "Alaska",
97 | "id": 1007,
98 | "isFeatured": true,
99 | "isFavorite": false,
100 | "park": "Charley Rivers National Preserve",
101 | "coordinates": {
102 | "longitude": -143.122586,
103 | "latitude": 65.350021
104 | },
105 | "imageName": "charleyrivers",
106 | },
107 | {
108 | "name": "Icy Bay",
109 | "category": "Mountains",
110 | "city": "Icy Bay",
111 | "state": "Alaska",
112 | "id": 1008,
113 | "isFeatured": false,
114 | "isFavorite": false,
115 | "park": "Wrangell-St. Elias National Park and Preserve",
116 | "coordinates": {
117 | "longitude": -141.518167,
118 | "latitude": 60.089917
119 | },
120 | "imageName": "icybay"
121 | },
122 | {
123 | "name": "Rainbow Lake",
124 | "category": "Lakes",
125 | "city": "Willow",
126 | "state": "Alaska",
127 | "id": 1009,
128 | "isFeatured": false,
129 | "isFavorite": false,
130 | "park": "State Recreation Area",
131 | "coordinates": {
132 | "longitude": -150.086103,
133 | "latitude": 61.694334
134 | },
135 | "imageName": "rainbowlake"
136 | },
137 | {
138 | "name": "Hidden Lake",
139 | "category": "Lakes",
140 | "city": "Newhalem",
141 | "state": "Washington",
142 | "id": 1010,
143 | "isFeatured": false,
144 | "isFavorite": false,
145 | "park": "North Cascades National Park",
146 | "coordinates": {
147 | "longitude": -121.17799,
148 | "latitude": 48.495442
149 | },
150 | "imageName": "hiddenlake"
151 | },
152 | {
153 | "name": "Chincoteague",
154 | "category": "Rivers",
155 | "city": "Chincoteague",
156 | "state": "Virginia",
157 | "id": 1011,
158 | "isFeatured": false,
159 | "isFavorite": false,
160 | "park": "Chincoteague National Wildlife Refuge",
161 | "coordinates": {
162 | "longitude": -75.383212,
163 | "latitude": 37.91531
164 | },
165 | "imageName": "chincoteague"
166 | },
167 | {
168 | "name": "Lake Umbagog",
169 | "category": "Lakes",
170 | "city": "Errol",
171 | "state": "New Hampshire",
172 | "id": 1012,
173 | "isFeatured": true,
174 | "isFavorite": false,
175 | "park": "Umbagog National Wildlife Refuge",
176 | "coordinates": {
177 | "longitude": -71.056816,
178 | "latitude": 44.747408
179 | },
180 | "imageName": "umbagog"
181 | }
182 | ]
183 |
--------------------------------------------------------------------------------
/Landmarks/Resources/rainbowlake.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksii-demedetskyi/HandlingUserInput/6047294e9fc6c5dcc7538eb4b7d3fae925d59c6b/Landmarks/Resources/rainbowlake.jpg
--------------------------------------------------------------------------------
/Landmarks/Resources/silversalmoncreek.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksii-demedetskyi/HandlingUserInput/6047294e9fc6c5dcc7538eb4b7d3fae925d59c6b/Landmarks/Resources/silversalmoncreek.jpg
--------------------------------------------------------------------------------
/Landmarks/Resources/stmarylake.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksii-demedetskyi/HandlingUserInput/6047294e9fc6c5dcc7538eb4b7d3fae925d59c6b/Landmarks/Resources/stmarylake.jpg
--------------------------------------------------------------------------------
/Landmarks/Resources/turtlerock.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksii-demedetskyi/HandlingUserInput/6047294e9fc6c5dcc7538eb4b7d3fae925d59c6b/Landmarks/Resources/turtlerock.jpg
--------------------------------------------------------------------------------
/Landmarks/Resources/twinlake.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksii-demedetskyi/HandlingUserInput/6047294e9fc6c5dcc7538eb4b7d3fae925d59c6b/Landmarks/Resources/twinlake.jpg
--------------------------------------------------------------------------------
/Landmarks/Resources/umbagog.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksii-demedetskyi/HandlingUserInput/6047294e9fc6c5dcc7538eb4b7d3fae925d59c6b/Landmarks/Resources/umbagog.jpg
--------------------------------------------------------------------------------
/Landmarks/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | /*
2 | See LICENSE folder for this sample’s licensing information.
3 |
4 | Abstract:
5 | The scene delegate.
6 | */
7 |
8 | import UIKit
9 | import SwiftUI
10 |
11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
12 |
13 | var window: UIWindow?
14 |
15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
16 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
17 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
18 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
19 |
20 | let userData = UserData()
21 |
22 | let initialState = State(
23 | landmarksByID: userData.landmarks.reduce(into: [:]) { map, landmark in
24 | map[landmark.id] = landmark
25 | },
26 | allLandmarks: userData.landmarks.map { $0.id },
27 | showFavoritesOnly: userData.showFavoritesOnly)
28 |
29 |
30 | let store = Store(initialState: initialState, reducer: Reduce.state)
31 |
32 | // Use a UIHostingController as window root view controller
33 | let window = UIWindow(frame: UIScreen.main.bounds)
34 | window.rootViewController = UIHostingController(rootView:
35 | StoreProvider(store: store) {
36 | LandmarkList(
37 | row: LandmarkRow.init,
38 | details: LandmarkDetail.init
39 | )
40 | }
41 | )
42 | self.window = window
43 | window.makeKeyAndVisible()
44 | }
45 |
46 | func sceneDidDisconnect(_ scene: UIScene) {
47 | // Called as the scene is being released by the system.
48 | // This occurs shortly after the scene enters the background, or when its session is discarded.
49 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
50 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
51 | }
52 |
53 | func sceneDidBecomeActive(_ scene: UIScene) {
54 | // Called when the scene has moved from an inactive state to an active state.
55 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
56 | }
57 |
58 | func sceneWillResignActive(_ scene: UIScene) {
59 | // Called when the scene will move from an active state to an inactive state.
60 | // This may occur due to temporary interruptions (ex. an incoming phone call).
61 | }
62 |
63 | func sceneWillEnterForeground(_ scene: UIScene) {
64 | // Called as the scene transitions from the background to the foreground.
65 | // Use this method to undo the changes made on entering the background.
66 | }
67 |
68 | func sceneDidEnterBackground(_ scene: UIScene) {
69 | // Called as the scene transitions from the foreground to the background.
70 | // Use this method to save data, release shared resources, and store enough scene-specific state information
71 | // to restore the scene back to its current state.
72 | }
73 |
74 | }
75 |
76 |
--------------------------------------------------------------------------------
/Landmarks/State.swift:
--------------------------------------------------------------------------------
1 | struct State: Reducable {
2 | let landmarksByID: [Landmark.ID: Landmark]
3 | let allLandmarks: [Landmark.ID]
4 | let showFavoritesOnly: Bool
5 | }
6 |
7 | extension Reduce {
8 | static let state = State.reduce.with { state, action in
9 | State(
10 | landmarksByID: Reduce.landmarksByID(state.landmarksByID, action),
11 | allLandmarks: Reduce.allLendmarks(state.allLandmarks, action),
12 | showFavoritesOnly: Reduce.showFavoritesOnly(state.showFavoritesOnly, action)
13 | )
14 | }
15 |
16 | static let landmarksByID = State.reduce.landmarksByID.withRules { match in
17 | match.on(Actions.ToggleLandmarkFavorite.self) { state, action in
18 | state[action.id]?.isFavorite.toggle()
19 | }
20 | }
21 |
22 | static let allLendmarks = State.reduce.allLandmarks.withConstant
23 |
24 | static let showFavoritesOnly = State.reduce.showFavoritesOnly.withRules { match in
25 | match.on(Actions.ToggleFavoritesOnly.self) { state, action in
26 | action.shouldShowFavorites
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Landmarks/Supporting Views/CircleImage.swift:
--------------------------------------------------------------------------------
1 | /*
2 | See LICENSE folder for this sample’s licensing information.
3 |
4 | Abstract:
5 | A view that clips an image to a circle and adds a stroke and shadow.
6 | */
7 |
8 | import SwiftUI
9 |
10 | struct CircleImage: View {
11 | var image: Image
12 |
13 | var body: some View {
14 | image
15 | .clipShape(Circle())
16 | .overlay(Circle().stroke(Color.white, lineWidth: 4))
17 | .shadow(radius: 10)
18 | }
19 | }
20 |
21 | #if DEBUG
22 | struct CircleImage_Previews: PreviewProvider {
23 | static var previews: some View {
24 | CircleImage(image: Image("turtlerock"))
25 | }
26 | }
27 | #endif
28 |
--------------------------------------------------------------------------------
/Landmarks/Supporting Views/MapView.swift:
--------------------------------------------------------------------------------
1 | /*
2 | See LICENSE folder for this sample’s licensing information.
3 |
4 | Abstract:
5 | A view that hosts an `MKMapView`.
6 | */
7 |
8 | import SwiftUI
9 | import MapKit
10 |
11 | struct MapView: UIViewRepresentable {
12 | var coordinate: CLLocationCoordinate2D
13 |
14 | func makeUIView(context: Context) -> MKMapView {
15 | MKMapView(frame: .zero)
16 | }
17 |
18 | func updateUIView(_ view: MKMapView, context: Context) {
19 | let span = MKCoordinateSpan(latitudeDelta: 0.02, longitudeDelta: 0.02)
20 | let region = MKCoordinateRegion(center: coordinate, span: span)
21 | view.setRegion(region, animated: true)
22 | }
23 | }
24 |
25 | #if DEBUG
26 | struct MapView_Previews: PreviewProvider {
27 | static var previews: some View {
28 | MapView(coordinate: landmarkData[0].locationCoordinate)
29 | }
30 | }
31 | #endif
32 |
--------------------------------------------------------------------------------
/Landmarks/Views/LandmarkDetail.swift:
--------------------------------------------------------------------------------
1 | /*
2 | See LICENSE folder for this sample’s licensing information.
3 |
4 | Abstract:
5 | A view showing the details for a landmark.
6 | */
7 |
8 | import SwiftUI
9 | import CoreLocation
10 |
11 | struct LandmarkDetail: ConnectedView {
12 | let id: Landmark.ID
13 |
14 | struct Props {
15 | let location: CLLocationCoordinate2D
16 | let imageName: String
17 | let name: String
18 | let toggleFavorite: () -> Void
19 | let isFavorite: Bool
20 | let park: String
21 | let state: String
22 | }
23 |
24 | func map(state: State, dispatch: @escaping (Action) -> Void) -> Props {
25 | guard let landmark = state.landmarksByID[id] else {
26 | fatalError("Id not found")
27 | }
28 |
29 | return Props(
30 | location: landmark.locationCoordinate,
31 | imageName: landmark.imageName,
32 | name: landmark.name,
33 | toggleFavorite: { dispatch(Actions.ToggleLandmarkFavorite(id: self.id)) },
34 | isFavorite: landmark.isFavorite,
35 | park: landmark.park,
36 | state: landmark.state)
37 | }
38 |
39 | static func body(props: Props) -> some View {
40 | VStack {
41 | MapView(coordinate: props.location)
42 | .edgesIgnoringSafeArea(.top)
43 | .frame(height: 300)
44 |
45 | CircleImage(image: ImageStore.shared.image(
46 | name: props.imageName,
47 | size: 250))
48 | .offset(x: 0, y: -130)
49 | .padding(.bottom, -130)
50 |
51 | VStack(alignment: .leading) {
52 | HStack {
53 | Text(verbatim: props.name)
54 | .font(.title)
55 |
56 | Button(action: props.toggleFavorite) {
57 | if props.isFavorite {
58 | Image(systemName: "star.fill")
59 | .foregroundColor(Color.yellow)
60 | } else {
61 | Image(systemName: "star")
62 | .foregroundColor(Color.gray)
63 | }
64 | }
65 | }
66 |
67 | HStack(alignment: .top) {
68 | Text(verbatim: props.park)
69 | .font(.subheadline)
70 | Spacer()
71 | Text(verbatim: props.state)
72 | .font(.subheadline)
73 | }
74 | }
75 | .padding()
76 |
77 | Spacer()
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/Landmarks/Views/LandmarkList.swift:
--------------------------------------------------------------------------------
1 | /*
2 | See LICENSE folder for this sample’s licensing information.
3 |
4 | Abstract:
5 | A view showing a list of landmarks.
6 | */
7 |
8 | import SwiftUI
9 |
10 | struct LandmarkList: ConnectedView {
11 | let row: (Landmark.ID) -> Row
12 | let details: (Landmark.ID) -> Details
13 |
14 | struct Props {
15 | let showFavoritesOnly: Binding
16 | let landmarks: [LandmarkItem]
17 |
18 | struct LandmarkItem: Identifiable {
19 | let id: Landmark.ID
20 | let row: Row
21 | let details: Details
22 | }
23 | }
24 |
25 | func map(state: State, dispatch: @escaping (Action) -> Void) -> Props {
26 | let showFavoritesOnly = Binding(
27 | getValue: { state.showFavoritesOnly },
28 | setValue: { dispatch(Actions.ToggleFavoritesOnly(shouldShowFavorites: $0))}
29 | )
30 |
31 | let allLandmarks = state.allLandmarks.compactMap { id in
32 | state.landmarksByID[id]
33 | }
34 |
35 | let visibleLandmarks = allLandmarks.filter { landmark in
36 | if state.showFavoritesOnly {
37 | return landmark.isFavorite
38 | } else {
39 | return true
40 | }
41 | }
42 |
43 | let landmarkItems = visibleLandmarks.map { landmark in
44 | LandmarkList.Props.LandmarkItem(
45 | id: landmark.id,
46 | row: row(landmark.id),
47 | details: details(landmark.id))
48 | }
49 |
50 | return Props(
51 | showFavoritesOnly: showFavoritesOnly,
52 | landmarks: landmarkItems)
53 | }
54 |
55 | static func body(props: Props) -> some View {
56 | NavigationView {
57 | List {
58 | Toggle(isOn: props.showFavoritesOnly) {
59 | Text("Show Favorites Only")
60 | }
61 |
62 | ForEach(props.landmarks) { landmark in
63 | NavigationButton(destination: landmark.details) {
64 | landmark.row
65 | }
66 | }
67 | }.navigationBarTitle(Text("Landmarks"), displayMode: .large)
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/Landmarks/Views/LandmarkRow.swift:
--------------------------------------------------------------------------------
1 | /*
2 | See LICENSE folder for this sample’s licensing information.
3 |
4 | Abstract:
5 | A single row to be displayed in a list of landmarks.
6 | */
7 |
8 | import SwiftUI
9 |
10 | struct LandmarkRow: ConnectedView {
11 | struct Props {
12 | let name: String
13 | let imageName: String
14 | let isFavorite: Bool
15 | }
16 |
17 | let id: Landmark.ID
18 |
19 | func map(state: State, dispatch: @escaping (Action) -> Void) -> Props {
20 | guard let landmark = state.landmarksByID[id] else {
21 | fatalError("Id not found")
22 | }
23 |
24 | return Props(name: landmark.name,
25 | imageName: landmark.imageName,
26 | isFavorite: landmark.isFavorite)
27 | }
28 |
29 | static func body(props: Props) -> some View {
30 | HStack {
31 | ImageStore.shared.image(name: props.imageName, size: 50)
32 | Text(verbatim: props.name)
33 | Spacer()
34 |
35 | if props.isFavorite {
36 | Image(systemName: "star.fill")
37 | .imageScale(.medium)
38 | .foregroundColor(.yellow)
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Redux/Action.swift:
--------------------------------------------------------------------------------
1 | protocol Action {}
2 |
3 | enum Actions {}
4 |
--------------------------------------------------------------------------------
/Redux/ConnectedView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | protocol ConnectedView: View {
4 | associatedtype State
5 | associatedtype Props
6 | associatedtype V: View
7 |
8 | func map(state: State, dispatch: @escaping (Action) -> Void) -> Props
9 | static func body(props: Props) -> V
10 | }
11 |
12 | extension ConnectedView {
13 | func render(state: State, dispatch: @escaping (Action) -> Void) -> V {
14 | let props = map(state: state, dispatch: dispatch)
15 | return Self.body(props: props)
16 | }
17 |
18 | var body: StoreConnector {
19 | return StoreConnector(content: render)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Redux/Reducable.swift:
--------------------------------------------------------------------------------
1 | protocol Reducable {}
2 |
3 |
4 | @dynamicMemberLookup
5 | struct ReducableWrapper {
6 | subscript(dynamicMember keyPath: KeyPath) -> ReducableWrapper {
7 | return ReducableWrapper()
8 | }
9 |
10 | func with(reducer: @escaping Reducer) -> Reducer {
11 | return reducer
12 | }
13 |
14 | func withInout(reducer: @escaping InoutReducer) -> Reducer {
15 | return { state, action in
16 | var state = state
17 | reducer(&state, action)
18 | return state
19 | }
20 | }
21 |
22 | var withConstant: Reducer {
23 | return { state, action in state }
24 | }
25 |
26 | func withRules(mathes: @escaping (inout Reduce.Match) -> Void) -> Reducer {
27 | return { state, action in
28 | var match = Reduce.Match(action: action, state: state)
29 | mathes(&match)
30 | return match.state
31 | }
32 | }
33 | }
34 |
35 | extension Reducable {
36 | static var reduce: ReducableWrapper { ReducableWrapper() }
37 | }
38 |
--------------------------------------------------------------------------------
/Redux/Reducer.swift:
--------------------------------------------------------------------------------
1 | typealias Reducer = (State, Action) -> State
2 | typealias InoutReducer = (inout State, Action) -> Void
3 |
4 | enum Reduce {
5 | struct Match {
6 | let action: Action
7 | var state: State
8 |
9 | mutating func on(_ type: A.Type, reduce: @escaping (State, A) -> State) {
10 | guard let action = action as? A else {
11 | return
12 | }
13 |
14 | state = reduce(state, action)
15 | }
16 |
17 | mutating func on(_ type: A.Type, reduce: @escaping (inout State, A) -> Void) {
18 | guard let action = action as? A else {
19 | return
20 | }
21 |
22 | reduce(&state, action)
23 | }
24 | }
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/Redux/Store.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import Combine
3 |
4 | final class Store: BindableObject {
5 | let didChange = PassthroughSubject()
6 |
7 | private(set) var state: S
8 | private let reducer: (S, Action) -> S
9 |
10 | init(initialState: S, reducer: @escaping (S, Action) -> S) {
11 | self.state = initialState
12 | self.reducer = reducer
13 | }
14 |
15 | func dispatch(action: Action) {
16 | state = reducer(state, action)
17 | didChange.send(state)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Redux/StoreConnector.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | struct StoreConnector: View {
4 | @EnvironmentObject var store: Store
5 | let content: (State, @escaping (Action) -> Void) -> V
6 |
7 | var body: V {
8 | content(store.state, store.dispatch(action:))
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/Redux/StoreProvider.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | struct StoreProvider: View {
4 | let store: Store
5 | let content: () -> V
6 |
7 | var body: some View {
8 | content().environmentObject(store)
9 | }
10 | }
11 |
--------------------------------------------------------------------------------