├── 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 | --------------------------------------------------------------------------------