├── .gitignore ├── LICENSE ├── MaterialDesignSearchUI ├── MaterialDesignSearchUI.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata ├── MaterialDesignSearchUI.xcworkspace │ └── contents.xcworkspacedata ├── MaterialDesignSearchUI │ ├── AppDelegate.swift │ ├── Info.plist │ ├── MainViewController.swift │ ├── Resources │ │ ├── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── ic_airport.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── icons8-airport-1.png │ │ │ │ ├── icons8-airport-2.png │ │ │ │ └── icons8-airport.png │ │ │ ├── ic_back.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── icons8-back-1.png │ │ │ │ ├── icons8-back-2.png │ │ │ │ └── icons8-back.png │ │ │ ├── ic_clear.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── icons8-multiply-1.png │ │ │ │ ├── icons8-multiply-2.png │ │ │ │ └── icons8-multiply.png │ │ │ ├── ic_coffee.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── icons8-coffee-1.png │ │ │ │ ├── icons8-coffee-2.png │ │ │ │ └── icons8-coffee.png │ │ │ ├── ic_gas.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── icons8-gas_station-1.png │ │ │ │ ├── icons8-gas_station-2.png │ │ │ │ └── icons8-gas_station.png │ │ │ ├── ic_locate.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── icons8-near_me-1.png │ │ │ │ ├── icons8-near_me-2.png │ │ │ │ └── icons8-near_me.png │ │ │ ├── ic_location.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── icons8-marker-1.png │ │ │ │ ├── icons8-marker-2.png │ │ │ │ └── icons8-marker.png │ │ │ ├── ic_parking.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── icons8-parking-1.png │ │ │ │ ├── icons8-parking-2.png │ │ │ │ └── icons8-parking.png │ │ │ ├── ic_restaurant.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── icons8-restaurant-1.png │ │ │ │ ├── icons8-restaurant-2.png │ │ │ │ └── icons8-restaurant.png │ │ │ ├── ic_search.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── icons8-search-1.png │ │ │ │ ├── icons8-search-2.png │ │ │ │ └── icons8-search.png │ │ │ └── ic_shopping.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── icons8-shopping-1.png │ │ │ │ ├── icons8-shopping-2.png │ │ │ │ └── icons8-shopping.png │ │ ├── Base.lproj │ │ │ └── LaunchScreen.storyboard │ │ ├── Fonts │ │ │ ├── Roboto-Bold.ttf │ │ │ └── Roboto-Regular.ttf │ │ └── ResManager.swift │ └── Source │ │ ├── AnimHelper.swift │ │ ├── Extensions.swift │ │ ├── SearchResultsView.swift │ │ └── Searchbar.swift ├── MaterialDesignSearchbarUITests │ ├── Info.plist │ └── MaterialDesignSearchbarUITests.swift ├── Podfile └── Podfile.lock ├── README.md └── gif ├── finalapp.gif ├── issue.gif ├── searchbar.gif └── searchresultsview.gif /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | # Package.resolved 41 | .build/ 42 | 43 | # CocoaPods 44 | # 45 | # We recommend against adding the Pods directory to your .gitignore. However 46 | # you should judge for yourself, the pros and cons are mentioned at: 47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 48 | # 49 | # Pods/ 50 | 51 | # Carthage 52 | # 53 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 54 | # Carthage/Checkouts 55 | 56 | Carthage/Build 57 | 58 | # fastlane 59 | # 60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 61 | # screenshots whenever they are needed. 62 | # For more information about the recommended setup visit: 63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 64 | 65 | fastlane/report.xml 66 | fastlane/Preview.html 67 | fastlane/screenshots/**/*.png 68 | fastlane/test_output 69 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Michael T. Ho 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 51; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 30037FBE22D986CF007A814D /* SearchResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30037FBD22D986CF007A814D /* SearchResultsView.swift */; }; 11 | 30037FC422DBAC4B007A814D /* ResManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30037FC322DBAC4B007A814D /* ResManager.swift */; }; 12 | 3046BD9E22D8380F00441F5C /* Roboto-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 30A8B31722D7EDF0005832A1 /* Roboto-Bold.ttf */; }; 13 | 3046BD9F22D8381200441F5C /* Roboto-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 30A8B31622D7EDF0005832A1 /* Roboto-Regular.ttf */; }; 14 | 30A8B2F022D7E34B005832A1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A8B2EF22D7E34B005832A1 /* AppDelegate.swift */; }; 15 | 30A8B2F222D7E34B005832A1 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A8B2F122D7E34B005832A1 /* MainViewController.swift */; }; 16 | 30A8B2F722D7E34C005832A1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 30A8B2F622D7E34C005832A1 /* Assets.xcassets */; }; 17 | 30A8B2FA22D7E34C005832A1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 30A8B2F822D7E34C005832A1 /* LaunchScreen.storyboard */; }; 18 | 30A8B30522D7E34C005832A1 /* MaterialDesignSearchbarUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A8B30422D7E34C005832A1 /* MaterialDesignSearchbarUITests.swift */; }; 19 | 30A8B31322D7E442005832A1 /* Searchbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A8B31222D7E442005832A1 /* Searchbar.swift */; }; 20 | 30A8B32022D7F888005832A1 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A8B31F22D7F888005832A1 /* Extensions.swift */; }; 21 | 30A8B32222D817B0005832A1 /* AnimHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A8B32122D817B0005832A1 /* AnimHelper.swift */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXContainerItemProxy section */ 25 | 30A8B30122D7E34C005832A1 /* PBXContainerItemProxy */ = { 26 | isa = PBXContainerItemProxy; 27 | containerPortal = 30A8B2E422D7E34B005832A1 /* Project object */; 28 | proxyType = 1; 29 | remoteGlobalIDString = 30A8B2EB22D7E34B005832A1; 30 | remoteInfo = MaterialDesignSearchbar; 31 | }; 32 | /* End PBXContainerItemProxy section */ 33 | 34 | /* Begin PBXFileReference section */ 35 | 30037FBD22D986CF007A814D /* SearchResultsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultsView.swift; sourceTree = ""; }; 36 | 30037FC322DBAC4B007A814D /* ResManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResManager.swift; sourceTree = ""; }; 37 | 30A8B2EC22D7E34B005832A1 /* MaterialDesignSearchUI.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MaterialDesignSearchUI.app; sourceTree = BUILT_PRODUCTS_DIR; }; 38 | 30A8B2EF22D7E34B005832A1 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 39 | 30A8B2F122D7E34B005832A1 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; 40 | 30A8B2F622D7E34C005832A1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 41 | 30A8B2F922D7E34C005832A1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 42 | 30A8B2FB22D7E34C005832A1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 43 | 30A8B30022D7E34C005832A1 /* MaterialDesignSearchUIUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MaterialDesignSearchUIUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | 30A8B30422D7E34C005832A1 /* MaterialDesignSearchbarUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaterialDesignSearchbarUITests.swift; sourceTree = ""; }; 45 | 30A8B30622D7E34C005832A1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 46 | 30A8B31222D7E442005832A1 /* Searchbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Searchbar.swift; sourceTree = ""; }; 47 | 30A8B31622D7EDF0005832A1 /* Roboto-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Roboto-Regular.ttf"; sourceTree = ""; }; 48 | 30A8B31722D7EDF0005832A1 /* Roboto-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Roboto-Bold.ttf"; sourceTree = ""; }; 49 | 30A8B31F22D7F888005832A1 /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; 50 | 30A8B32122D817B0005832A1 /* AnimHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimHelper.swift; sourceTree = ""; }; 51 | /* End PBXFileReference section */ 52 | 53 | /* Begin PBXFrameworksBuildPhase section */ 54 | 30A8B2E922D7E34B005832A1 /* Frameworks */ = { 55 | isa = PBXFrameworksBuildPhase; 56 | buildActionMask = 2147483647; 57 | files = ( 58 | ); 59 | runOnlyForDeploymentPostprocessing = 0; 60 | }; 61 | 30A8B2FD22D7E34C005832A1 /* Frameworks */ = { 62 | isa = PBXFrameworksBuildPhase; 63 | buildActionMask = 2147483647; 64 | files = ( 65 | ); 66 | runOnlyForDeploymentPostprocessing = 0; 67 | }; 68 | /* End PBXFrameworksBuildPhase section */ 69 | 70 | /* Begin PBXGroup section */ 71 | 30A8B2E322D7E34B005832A1 = { 72 | isa = PBXGroup; 73 | children = ( 74 | 30A8B2EE22D7E34B005832A1 /* MaterialDesignSearchUI */, 75 | 30A8B30322D7E34C005832A1 /* MaterialDesignSearchbarUITests */, 76 | 30A8B2ED22D7E34B005832A1 /* Products */, 77 | 9E95DB1065D42AF7EF06F0C9 /* Pods */, 78 | ); 79 | sourceTree = ""; 80 | }; 81 | 30A8B2ED22D7E34B005832A1 /* Products */ = { 82 | isa = PBXGroup; 83 | children = ( 84 | 30A8B2EC22D7E34B005832A1 /* MaterialDesignSearchUI.app */, 85 | 30A8B30022D7E34C005832A1 /* MaterialDesignSearchUIUITests.xctest */, 86 | ); 87 | name = Products; 88 | sourceTree = ""; 89 | }; 90 | 30A8B2EE22D7E34B005832A1 /* MaterialDesignSearchUI */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | 30A8B31122D7E41A005832A1 /* Source */, 94 | 30A8B2EF22D7E34B005832A1 /* AppDelegate.swift */, 95 | 30A8B2F122D7E34B005832A1 /* MainViewController.swift */, 96 | 30A8B31422D7EDC6005832A1 /* Resources */, 97 | 30A8B2FB22D7E34C005832A1 /* Info.plist */, 98 | ); 99 | path = MaterialDesignSearchUI; 100 | sourceTree = ""; 101 | }; 102 | 30A8B30322D7E34C005832A1 /* MaterialDesignSearchbarUITests */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | 30A8B30422D7E34C005832A1 /* MaterialDesignSearchbarUITests.swift */, 106 | 30A8B30622D7E34C005832A1 /* Info.plist */, 107 | ); 108 | path = MaterialDesignSearchbarUITests; 109 | sourceTree = ""; 110 | }; 111 | 30A8B31122D7E41A005832A1 /* Source */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | 30A8B31F22D7F888005832A1 /* Extensions.swift */, 115 | 30A8B31222D7E442005832A1 /* Searchbar.swift */, 116 | 30037FBD22D986CF007A814D /* SearchResultsView.swift */, 117 | 30A8B32122D817B0005832A1 /* AnimHelper.swift */, 118 | ); 119 | path = Source; 120 | sourceTree = ""; 121 | }; 122 | 30A8B31422D7EDC6005832A1 /* Resources */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | 30A8B31522D7EDDC005832A1 /* Fonts */, 126 | 30037FC322DBAC4B007A814D /* ResManager.swift */, 127 | 30A8B2F622D7E34C005832A1 /* Assets.xcassets */, 128 | 30A8B2F822D7E34C005832A1 /* LaunchScreen.storyboard */, 129 | ); 130 | path = Resources; 131 | sourceTree = ""; 132 | }; 133 | 30A8B31522D7EDDC005832A1 /* Fonts */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | 30A8B31722D7EDF0005832A1 /* Roboto-Bold.ttf */, 137 | 30A8B31622D7EDF0005832A1 /* Roboto-Regular.ttf */, 138 | ); 139 | path = Fonts; 140 | sourceTree = ""; 141 | }; 142 | 9E95DB1065D42AF7EF06F0C9 /* Pods */ = { 143 | isa = PBXGroup; 144 | children = ( 145 | ); 146 | path = Pods; 147 | sourceTree = ""; 148 | }; 149 | /* End PBXGroup section */ 150 | 151 | /* Begin PBXNativeTarget section */ 152 | 30A8B2EB22D7E34B005832A1 /* MaterialDesignSearchUI */ = { 153 | isa = PBXNativeTarget; 154 | buildConfigurationList = 30A8B30922D7E34C005832A1 /* Build configuration list for PBXNativeTarget "MaterialDesignSearchUI" */; 155 | buildPhases = ( 156 | 30A8B2E822D7E34B005832A1 /* Sources */, 157 | 30A8B2E922D7E34B005832A1 /* Frameworks */, 158 | 30A8B2EA22D7E34B005832A1 /* Resources */, 159 | ); 160 | buildRules = ( 161 | ); 162 | dependencies = ( 163 | ); 164 | name = MaterialDesignSearchUI; 165 | productName = MaterialDesignSearchbar; 166 | productReference = 30A8B2EC22D7E34B005832A1 /* MaterialDesignSearchUI.app */; 167 | productType = "com.apple.product-type.application"; 168 | }; 169 | 30A8B2FF22D7E34C005832A1 /* MaterialDesignSearchUIUITests */ = { 170 | isa = PBXNativeTarget; 171 | buildConfigurationList = 30A8B30C22D7E34C005832A1 /* Build configuration list for PBXNativeTarget "MaterialDesignSearchUIUITests" */; 172 | buildPhases = ( 173 | 30A8B2FC22D7E34C005832A1 /* Sources */, 174 | 30A8B2FD22D7E34C005832A1 /* Frameworks */, 175 | 30A8B2FE22D7E34C005832A1 /* Resources */, 176 | ); 177 | buildRules = ( 178 | ); 179 | dependencies = ( 180 | 30A8B30222D7E34C005832A1 /* PBXTargetDependency */, 181 | ); 182 | name = MaterialDesignSearchUIUITests; 183 | productName = MaterialDesignSearchbarUITests; 184 | productReference = 30A8B30022D7E34C005832A1 /* MaterialDesignSearchUIUITests.xctest */; 185 | productType = "com.apple.product-type.bundle.ui-testing"; 186 | }; 187 | /* End PBXNativeTarget section */ 188 | 189 | /* Begin PBXProject section */ 190 | 30A8B2E422D7E34B005832A1 /* Project object */ = { 191 | isa = PBXProject; 192 | attributes = { 193 | LastSwiftUpdateCheck = 1020; 194 | LastUpgradeCheck = 1020; 195 | ORGANIZATIONNAME = "Ho, Tsungwei"; 196 | TargetAttributes = { 197 | 30A8B2EB22D7E34B005832A1 = { 198 | CreatedOnToolsVersion = 10.2.1; 199 | SystemCapabilities = { 200 | com.apple.BackgroundModes = { 201 | enabled = 1; 202 | }; 203 | }; 204 | }; 205 | 30A8B2FF22D7E34C005832A1 = { 206 | CreatedOnToolsVersion = 10.2.1; 207 | TestTargetID = 30A8B2EB22D7E34B005832A1; 208 | }; 209 | }; 210 | }; 211 | buildConfigurationList = 30A8B2E722D7E34B005832A1 /* Build configuration list for PBXProject "MaterialDesignSearchUI" */; 212 | compatibilityVersion = "Xcode 9.3"; 213 | developmentRegion = en; 214 | hasScannedForEncodings = 0; 215 | knownRegions = ( 216 | en, 217 | Base, 218 | ); 219 | mainGroup = 30A8B2E322D7E34B005832A1; 220 | productRefGroup = 30A8B2ED22D7E34B005832A1 /* Products */; 221 | projectDirPath = ""; 222 | projectRoot = ""; 223 | targets = ( 224 | 30A8B2EB22D7E34B005832A1 /* MaterialDesignSearchUI */, 225 | 30A8B2FF22D7E34C005832A1 /* MaterialDesignSearchUIUITests */, 226 | ); 227 | }; 228 | /* End PBXProject section */ 229 | 230 | /* Begin PBXResourcesBuildPhase section */ 231 | 30A8B2EA22D7E34B005832A1 /* Resources */ = { 232 | isa = PBXResourcesBuildPhase; 233 | buildActionMask = 2147483647; 234 | files = ( 235 | 30A8B2FA22D7E34C005832A1 /* LaunchScreen.storyboard in Resources */, 236 | 30A8B2F722D7E34C005832A1 /* Assets.xcassets in Resources */, 237 | 3046BD9F22D8381200441F5C /* Roboto-Regular.ttf in Resources */, 238 | 3046BD9E22D8380F00441F5C /* Roboto-Bold.ttf in Resources */, 239 | ); 240 | runOnlyForDeploymentPostprocessing = 0; 241 | }; 242 | 30A8B2FE22D7E34C005832A1 /* Resources */ = { 243 | isa = PBXResourcesBuildPhase; 244 | buildActionMask = 2147483647; 245 | files = ( 246 | ); 247 | runOnlyForDeploymentPostprocessing = 0; 248 | }; 249 | /* End PBXResourcesBuildPhase section */ 250 | 251 | /* Begin PBXSourcesBuildPhase section */ 252 | 30A8B2E822D7E34B005832A1 /* Sources */ = { 253 | isa = PBXSourcesBuildPhase; 254 | buildActionMask = 2147483647; 255 | files = ( 256 | 30A8B32222D817B0005832A1 /* AnimHelper.swift in Sources */, 257 | 30A8B2F222D7E34B005832A1 /* MainViewController.swift in Sources */, 258 | 30A8B31322D7E442005832A1 /* Searchbar.swift in Sources */, 259 | 30A8B32022D7F888005832A1 /* Extensions.swift in Sources */, 260 | 30037FC422DBAC4B007A814D /* ResManager.swift in Sources */, 261 | 30037FBE22D986CF007A814D /* SearchResultsView.swift in Sources */, 262 | 30A8B2F022D7E34B005832A1 /* AppDelegate.swift in Sources */, 263 | ); 264 | runOnlyForDeploymentPostprocessing = 0; 265 | }; 266 | 30A8B2FC22D7E34C005832A1 /* Sources */ = { 267 | isa = PBXSourcesBuildPhase; 268 | buildActionMask = 2147483647; 269 | files = ( 270 | 30A8B30522D7E34C005832A1 /* MaterialDesignSearchbarUITests.swift in Sources */, 271 | ); 272 | runOnlyForDeploymentPostprocessing = 0; 273 | }; 274 | /* End PBXSourcesBuildPhase section */ 275 | 276 | /* Begin PBXTargetDependency section */ 277 | 30A8B30222D7E34C005832A1 /* PBXTargetDependency */ = { 278 | isa = PBXTargetDependency; 279 | target = 30A8B2EB22D7E34B005832A1 /* MaterialDesignSearchUI */; 280 | targetProxy = 30A8B30122D7E34C005832A1 /* PBXContainerItemProxy */; 281 | }; 282 | /* End PBXTargetDependency section */ 283 | 284 | /* Begin PBXVariantGroup section */ 285 | 30A8B2F822D7E34C005832A1 /* LaunchScreen.storyboard */ = { 286 | isa = PBXVariantGroup; 287 | children = ( 288 | 30A8B2F922D7E34C005832A1 /* Base */, 289 | ); 290 | name = LaunchScreen.storyboard; 291 | sourceTree = ""; 292 | }; 293 | /* End PBXVariantGroup section */ 294 | 295 | /* Begin XCBuildConfiguration section */ 296 | 30A8B30722D7E34C005832A1 /* Debug */ = { 297 | isa = XCBuildConfiguration; 298 | buildSettings = { 299 | ALWAYS_SEARCH_USER_PATHS = NO; 300 | CLANG_ANALYZER_NONNULL = YES; 301 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 302 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 303 | CLANG_CXX_LIBRARY = "libc++"; 304 | CLANG_ENABLE_MODULES = YES; 305 | CLANG_ENABLE_OBJC_ARC = YES; 306 | CLANG_ENABLE_OBJC_WEAK = YES; 307 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 308 | CLANG_WARN_BOOL_CONVERSION = YES; 309 | CLANG_WARN_COMMA = YES; 310 | CLANG_WARN_CONSTANT_CONVERSION = YES; 311 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 312 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 313 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 314 | CLANG_WARN_EMPTY_BODY = YES; 315 | CLANG_WARN_ENUM_CONVERSION = YES; 316 | CLANG_WARN_INFINITE_RECURSION = YES; 317 | CLANG_WARN_INT_CONVERSION = YES; 318 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 319 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 320 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 321 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 322 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 323 | CLANG_WARN_STRICT_PROTOTYPES = YES; 324 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 325 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 326 | CLANG_WARN_UNREACHABLE_CODE = YES; 327 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 328 | CODE_SIGN_IDENTITY = "iPhone Developer"; 329 | COPY_PHASE_STRIP = NO; 330 | DEBUG_INFORMATION_FORMAT = dwarf; 331 | ENABLE_STRICT_OBJC_MSGSEND = YES; 332 | ENABLE_TESTABILITY = YES; 333 | GCC_C_LANGUAGE_STANDARD = gnu11; 334 | GCC_DYNAMIC_NO_PIC = NO; 335 | GCC_NO_COMMON_BLOCKS = YES; 336 | GCC_OPTIMIZATION_LEVEL = 0; 337 | GCC_PREPROCESSOR_DEFINITIONS = ( 338 | "DEBUG=1", 339 | "$(inherited)", 340 | ); 341 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 342 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 343 | GCC_WARN_UNDECLARED_SELECTOR = YES; 344 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 345 | GCC_WARN_UNUSED_FUNCTION = YES; 346 | GCC_WARN_UNUSED_VARIABLE = YES; 347 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 348 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 349 | MTL_FAST_MATH = YES; 350 | ONLY_ACTIVE_ARCH = YES; 351 | SDKROOT = iphoneos; 352 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 353 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 354 | }; 355 | name = Debug; 356 | }; 357 | 30A8B30822D7E34C005832A1 /* Release */ = { 358 | isa = XCBuildConfiguration; 359 | buildSettings = { 360 | ALWAYS_SEARCH_USER_PATHS = NO; 361 | CLANG_ANALYZER_NONNULL = YES; 362 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 363 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 364 | CLANG_CXX_LIBRARY = "libc++"; 365 | CLANG_ENABLE_MODULES = YES; 366 | CLANG_ENABLE_OBJC_ARC = YES; 367 | CLANG_ENABLE_OBJC_WEAK = YES; 368 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 369 | CLANG_WARN_BOOL_CONVERSION = YES; 370 | CLANG_WARN_COMMA = YES; 371 | CLANG_WARN_CONSTANT_CONVERSION = YES; 372 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 373 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 374 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 375 | CLANG_WARN_EMPTY_BODY = YES; 376 | CLANG_WARN_ENUM_CONVERSION = YES; 377 | CLANG_WARN_INFINITE_RECURSION = YES; 378 | CLANG_WARN_INT_CONVERSION = YES; 379 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 380 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 381 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 382 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 383 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 384 | CLANG_WARN_STRICT_PROTOTYPES = YES; 385 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 386 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 387 | CLANG_WARN_UNREACHABLE_CODE = YES; 388 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 389 | CODE_SIGN_IDENTITY = "iPhone Developer"; 390 | COPY_PHASE_STRIP = NO; 391 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 392 | ENABLE_NS_ASSERTIONS = NO; 393 | ENABLE_STRICT_OBJC_MSGSEND = YES; 394 | GCC_C_LANGUAGE_STANDARD = gnu11; 395 | GCC_NO_COMMON_BLOCKS = YES; 396 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 397 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 398 | GCC_WARN_UNDECLARED_SELECTOR = YES; 399 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 400 | GCC_WARN_UNUSED_FUNCTION = YES; 401 | GCC_WARN_UNUSED_VARIABLE = YES; 402 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 403 | MTL_ENABLE_DEBUG_INFO = NO; 404 | MTL_FAST_MATH = YES; 405 | SDKROOT = iphoneos; 406 | SWIFT_COMPILATION_MODE = wholemodule; 407 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 408 | VALIDATE_PRODUCT = YES; 409 | }; 410 | name = Release; 411 | }; 412 | 30A8B30A22D7E34C005832A1 /* Debug */ = { 413 | isa = XCBuildConfiguration; 414 | buildSettings = { 415 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 416 | CODE_SIGN_STYLE = Automatic; 417 | DEVELOPMENT_TEAM = XM5XV26R5J; 418 | INFOPLIST_FILE = MaterialDesignSearchUI/Info.plist; 419 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 420 | LD_RUNPATH_SEARCH_PATHS = ( 421 | "$(inherited)", 422 | "@executable_path/Frameworks", 423 | ); 424 | PRODUCT_BUNDLE_IDENTIFIER = com.michaelho.MaterialDesignSearchUI5; 425 | PRODUCT_NAME = "$(TARGET_NAME)"; 426 | SWIFT_VERSION = 5.0; 427 | TARGETED_DEVICE_FAMILY = "1,2"; 428 | }; 429 | name = Debug; 430 | }; 431 | 30A8B30B22D7E34C005832A1 /* Release */ = { 432 | isa = XCBuildConfiguration; 433 | buildSettings = { 434 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 435 | CODE_SIGN_STYLE = Automatic; 436 | DEVELOPMENT_TEAM = XM5XV26R5J; 437 | INFOPLIST_FILE = MaterialDesignSearchUI/Info.plist; 438 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 439 | LD_RUNPATH_SEARCH_PATHS = ( 440 | "$(inherited)", 441 | "@executable_path/Frameworks", 442 | ); 443 | PRODUCT_BUNDLE_IDENTIFIER = com.michaelho.MaterialDesignSearchUI5; 444 | PRODUCT_NAME = "$(TARGET_NAME)"; 445 | SWIFT_VERSION = 5.0; 446 | TARGETED_DEVICE_FAMILY = "1,2"; 447 | }; 448 | name = Release; 449 | }; 450 | 30A8B30D22D7E34C005832A1 /* Debug */ = { 451 | isa = XCBuildConfiguration; 452 | buildSettings = { 453 | CODE_SIGN_IDENTITY = "Apple Development"; 454 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; 455 | CODE_SIGN_STYLE = Automatic; 456 | DEVELOPMENT_TEAM = XM5XV26R5J; 457 | INFOPLIST_FILE = MaterialDesignSearchbarUITests/Info.plist; 458 | LD_RUNPATH_SEARCH_PATHS = ( 459 | "$(inherited)", 460 | "@executable_path/Frameworks", 461 | "@loader_path/Frameworks", 462 | ); 463 | PRODUCT_BUNDLE_IDENTIFIER = com.michaelho.MaterialDesignSearchbarUITests; 464 | PRODUCT_NAME = "$(TARGET_NAME)"; 465 | PROVISIONING_PROFILE_SPECIFIER = ""; 466 | "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; 467 | SWIFT_VERSION = 5.0; 468 | TARGETED_DEVICE_FAMILY = "1,2"; 469 | TEST_TARGET_NAME = MaterialDesignSearchbar; 470 | }; 471 | name = Debug; 472 | }; 473 | 30A8B30E22D7E34C005832A1 /* Release */ = { 474 | isa = XCBuildConfiguration; 475 | buildSettings = { 476 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; 477 | CODE_SIGN_STYLE = Automatic; 478 | DEVELOPMENT_TEAM = XM5XV26R5J; 479 | INFOPLIST_FILE = MaterialDesignSearchbarUITests/Info.plist; 480 | LD_RUNPATH_SEARCH_PATHS = ( 481 | "$(inherited)", 482 | "@executable_path/Frameworks", 483 | "@loader_path/Frameworks", 484 | ); 485 | PRODUCT_BUNDLE_IDENTIFIER = com.michaelho.MaterialDesignSearchbarUITests; 486 | PRODUCT_NAME = "$(TARGET_NAME)"; 487 | SWIFT_VERSION = 5.0; 488 | TARGETED_DEVICE_FAMILY = "1,2"; 489 | TEST_TARGET_NAME = MaterialDesignSearchbar; 490 | }; 491 | name = Release; 492 | }; 493 | /* End XCBuildConfiguration section */ 494 | 495 | /* Begin XCConfigurationList section */ 496 | 30A8B2E722D7E34B005832A1 /* Build configuration list for PBXProject "MaterialDesignSearchUI" */ = { 497 | isa = XCConfigurationList; 498 | buildConfigurations = ( 499 | 30A8B30722D7E34C005832A1 /* Debug */, 500 | 30A8B30822D7E34C005832A1 /* Release */, 501 | ); 502 | defaultConfigurationIsVisible = 0; 503 | defaultConfigurationName = Release; 504 | }; 505 | 30A8B30922D7E34C005832A1 /* Build configuration list for PBXNativeTarget "MaterialDesignSearchUI" */ = { 506 | isa = XCConfigurationList; 507 | buildConfigurations = ( 508 | 30A8B30A22D7E34C005832A1 /* Debug */, 509 | 30A8B30B22D7E34C005832A1 /* Release */, 510 | ); 511 | defaultConfigurationIsVisible = 0; 512 | defaultConfigurationName = Release; 513 | }; 514 | 30A8B30C22D7E34C005832A1 /* Build configuration list for PBXNativeTarget "MaterialDesignSearchUIUITests" */ = { 515 | isa = XCConfigurationList; 516 | buildConfigurations = ( 517 | 30A8B30D22D7E34C005832A1 /* Debug */, 518 | 30A8B30E22D7E34C005832A1 /* Release */, 519 | ); 520 | defaultConfigurationIsVisible = 0; 521 | defaultConfigurationName = Release; 522 | }; 523 | /* End XCConfigurationList section */ 524 | }; 525 | rootObject = 30A8B2E422D7E34B005832A1 /* Project object */; 526 | } 527 | -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // MaterialDesignSearchbar 4 | // 5 | // Created by Ho, Tsungwei on 7/11/19. 6 | // Copyright © 2019 Ho, Tsungwei. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreLocation 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | private let LOGTAG = "[AppDelegate] " 15 | 16 | var window: UIWindow? 17 | var latestLocation: CLLocation? 18 | var locationMgr = CLLocationManager() 19 | var uiStyle: SearchUserInterfaceStyle = .light 20 | 21 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 22 | // Set global state for userInterfaceStyle 23 | if #available(iOS 13.0, *), UITraitCollection.current.userInterfaceStyle == .dark { 24 | uiStyle = .dark 25 | } 26 | window = UIWindow(frame: UIScreen.main.bounds) 27 | let mainView = MainViewController() 28 | self.window!.rootViewController = mainView 29 | self.window?.makeKeyAndVisible() 30 | setupLocationManager() 31 | return true 32 | } 33 | /** 34 | Set up location manager. 35 | */ 36 | func setupLocationManager() { 37 | locationMgr.allowsBackgroundLocationUpdates = true 38 | locationMgr.delegate = self 39 | locationMgr.startUpdatingLocation() 40 | } 41 | } 42 | // MARK: - CLLocationManagerDelegate 43 | extension AppDelegate: CLLocationManagerDelegate { 44 | func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { 45 | latestLocation = locations.last != nil ? locations.last! : latestLocation 46 | } 47 | 48 | func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { 49 | print("\(self.LOGTAG) didFailWithError \(error)") 50 | } 51 | } 52 | 53 | enum SearchUserInterfaceStyle { 54 | case light 55 | case dark 56 | } 57 | -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/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 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSLocationAlwaysAndWhenInUseUsageDescription 24 | Need to access location. 25 | NSLocationWhenInUseUsageDescription 26 | Need to access location. 27 | UIAppFonts 28 | 29 | Roboto-Bold.ttf 30 | Roboto-Regular.ttf 31 | 32 | UIBackgroundModes 33 | 34 | location 35 | 36 | UILaunchStoryboardName 37 | LaunchScreen 38 | UIRequiredDeviceCapabilities 39 | 40 | armv7 41 | 42 | UISupportedInterfaceOrientations 43 | 44 | UIInterfaceOrientationPortrait 45 | 46 | UISupportedInterfaceOrientations~ipad 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationPortraitUpsideDown 50 | UIInterfaceOrientationLandscapeLeft 51 | UIInterfaceOrientationLandscapeRight 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/MainViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // MaterialDesignSearchbar 4 | // 5 | // Created by Ho, Tsungwei on 7/11/19. 6 | // Copyright © 2019 Ho, Tsungwei. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import MapKit 11 | import MaterialDesignWidgets 12 | 13 | class MainViewController: UIViewController { 14 | // MARK: - UI widgets 15 | private var searchbar: Searchbar! 16 | private var maskView: UIView! 17 | private var searchResultsView: SearchResultsView! 18 | private var mapView: MKMapView! 19 | private var btnLocate: MaterialButton! 20 | // MARK: - Private properties 21 | private var dim: CGSize = .zero 22 | private var foregroundColor = UIColor.darkGray 23 | private let animHplr = AnimHelper.shared 24 | // viewDidLoad 25 | override func viewDidLoad() { 26 | super.viewDidLoad() 27 | dim = self.view.frame.size 28 | initUI() 29 | requestPermission() 30 | btnLocate.addTarget(self, action: #selector(tapLocate), for: .touchUpInside) 31 | } 32 | /** 33 | Init UI. 34 | */ 35 | private func initUI() { 36 | self.view.backgroundColor = ResManager.Colors.sand 37 | maskView = UIView() 38 | maskView.backgroundColor = self.appDelegate.uiStyle == .dark ? .darkGray : .white 39 | // Configure searchbar 40 | searchbar = Searchbar( 41 | onStartSearch: { [weak self] (isSearching) in 42 | guard let self = self else { return } 43 | self.showSearchResultsView(isSearching) 44 | }, 45 | onClearInput: { [weak self] in 46 | guard let self = self else { return } 47 | self.searchResultsView.state = .populated([]) 48 | self.mapView.removeAnnotations(self.mapView.annotations) 49 | }, 50 | delegate: self 51 | ) 52 | // Configure searchResultsView 53 | searchResultsView = SearchResultsView(didSelectAction: { [weak self] (placemark) in 54 | guard let self = self else { return } 55 | self.didSelectPlacemark(placemark) 56 | }) 57 | showSearchResultsView(false) 58 | // Set up mapView 59 | mapView = MKMapView() 60 | mapView.delegate = self 61 | btnLocate = MaterialButton( 62 | icon: #imageLiteral(resourceName: "ic_locate").colored( self.appDelegate.uiStyle == .dark ? .white : .darkGray), 63 | bgColor: self.appDelegate.uiStyle == .dark ? .darkGray : .white, 64 | cornerRadius: 0.15*dim.width/2, 65 | withShadow: true 66 | ) 67 | self.view.addSubViews([mapView, btnLocate, maskView, searchResultsView, searchbar]) 68 | } 69 | 70 | override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { 71 | if #available(iOS 13.0, *) { 72 | btnLocate.backgroundColor = UITraitCollection.current.userInterfaceStyle == .dark ? .darkGray : .white 73 | } 74 | } 75 | 76 | private func requestPermission() { 77 | appDelegate.locationMgr.desiredAccuracy = kCLLocationAccuracyBest 78 | appDelegate.locationMgr.requestAlwaysAuthorization() 79 | } 80 | 81 | @objc private func tapLocate() { 82 | mapView.setUserTrackingMode(.follow, animated: true) 83 | } 84 | 85 | private func didSelectPlacemark(_ placemark: CLPlacemark) { 86 | guard let loc = placemark.location else { return } 87 | // Set search bar 88 | self.searchbar.textInput.text = placemark.name 89 | self.searchbar.textFieldDidEndEditing(self.searchbar.textInput) 90 | // Add annotation 91 | let annotation = MKPointAnnotation() 92 | annotation.title = placemark.name 93 | annotation.coordinate = loc.coordinate 94 | self.mapView.showAnnotations([annotation], animated: true) 95 | } 96 | 97 | private func showSearchResultsView(_ show: Bool) { 98 | if show { 99 | guard maskView.alpha == 0.0 else { return } 100 | animHplr.showViewsByMovingUp([maskView, searchResultsView]) 101 | } else { 102 | animHplr.hideViewsByMovingDown([maskView, searchResultsView]) 103 | searchResultsView.isScrolling = false 104 | } 105 | } 106 | // viewDidLayoutSubviews 107 | override func viewDidLayoutSubviews() { 108 | super.viewDidLayoutSubviews() 109 | maskView.setConstraintsToView(top: self.view, bottom: self.view, left: self.view, right: self.view) 110 | mapView.setConstraintsToView(top: self.view, bottom: self.view, left: self.view, right: self.view) 111 | btnLocate.setConstraintsToView(bottom: self.view, bConst: -0.05*dim.height, right: searchbar) 112 | self.view.addConstraints([ 113 | NSLayoutConstraint(item: searchbar as Any, attribute: .top, 114 | relatedBy: .equal, 115 | toItem: self.view, attribute: .top, 116 | multiplier: 1.0, constant: 0.1*self.dim.height), 117 | NSLayoutConstraint(item: searchbar as Any, attribute: .centerX, 118 | relatedBy: .equal, 119 | toItem: self.view, attribute: .centerX, 120 | multiplier: 1.0, constant: 0.0), 121 | NSLayoutConstraint(item: searchResultsView as Any, attribute: .centerX, 122 | relatedBy: .equal, 123 | toItem: searchbar, attribute: .centerX, 124 | multiplier: 1.0, constant: 0.0), 125 | NSLayoutConstraint(item: searchResultsView as Any, attribute: .top, 126 | relatedBy: .equal, 127 | toItem: searchbar, attribute: .bottom, 128 | multiplier: 1.0, constant: 0.02*dim.height) 129 | ]) 130 | searchResultsView.setConstraintsToView(bottom: maskView, left: searchbar, right: searchbar) 131 | searchbar.setHeightConstraint(0.07*dim.height) 132 | searchbar.setWidthConstraint(0.9*dim.width) 133 | // Set the corner radius to be half of the button height to make it circular. 134 | btnLocate.setHeightConstraint(0.15*dim.width) 135 | btnLocate.setWidthConstraint(0.15*dim.width) 136 | self.view.layoutIfNeeded() 137 | } 138 | // viewDidAppear 139 | override func viewDidAppear(_ animated: Bool) { 140 | super.viewDidAppear(true) 141 | (mapView.isZoomEnabled, mapView.showsUserLocation) = (true, true) 142 | mapView.setUserTrackingMode(.follow, animated: true) 143 | } 144 | } 145 | // MARK: - SearchbarDelegate 146 | extension MainViewController: SearchbarDelegate { 147 | 148 | func searchbarTextDidChange(_ textField: UITextField) { 149 | guard let keyword = isTextInputValid(textField) else { return } 150 | searchResultsView.state = .loading 151 | searchLocations(keyword) 152 | } 153 | 154 | private func isTextInputValid(_ textField: UITextField) -> String? { 155 | if let keyword = textField.text, !keyword.isEmpty { return keyword } 156 | return nil 157 | } 158 | 159 | func textFieldDidBeginEditing(_ textField: UITextField) { 160 | // clear the previous search results 161 | self.searchResultsView.state = .populated([]) 162 | showSearchResultsView(true) 163 | } 164 | 165 | func textFieldDidEndEditing(_ textField: UITextField) { 166 | guard !searchResultsView.isScrolling else { return } 167 | showSearchResultsView(false) 168 | searchbar.setBtnStates(hasInput: textField.text?.count != 0, isSearching: false) 169 | } 170 | 171 | func searchbarTextShouldReturn(_ textField: UITextField) -> Bool { 172 | guard let keyword = isTextInputValid(textField) else { return false } 173 | searchLocations(keyword, completion: { [weak self] (placemarks, error) in 174 | guard let self = self, let first = placemarks.first else { return } 175 | self.didSelectPlacemark(first) 176 | }) 177 | return true 178 | } 179 | /** 180 | Search locations by keyword entered in search bar. 181 | 182 | - Parameter keyword: The keyword as the input to search locations. 183 | - Parameter completion: The completion handler after showing search results. 184 | */ 185 | private func searchLocations(_ keyword: String, completion: (([CLPlacemark], Error?) -> Void)? = nil) { 186 | DispatchQueue.global(qos: .userInteractive).async { [weak self] in 187 | guard let self = self else { return } 188 | let request = MKLocalSearch.Request() 189 | request.naturalLanguageQuery = keyword 190 | request.region = self.mapView.region 191 | let search = MKLocalSearch(request: request) 192 | search.start { response, error in 193 | var placemarks = [CLPlacemark]() 194 | if let response = response { 195 | for item in response.mapItems { 196 | placemarks.append(item.placemark) 197 | } 198 | } 199 | DispatchQueue.main.async { 200 | self.searchResultsView.update(newPlacemarks: placemarks, error: error) 201 | completion?(placemarks, error) 202 | } 203 | } 204 | } 205 | } 206 | } 207 | // MARK: - MKMapViewDelegate 208 | extension MainViewController: MKMapViewDelegate { 209 | // viewForAnnotation 210 | // Refer to https://hackingwithswift.com 211 | func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { 212 | guard annotation is MKPointAnnotation else { return nil } 213 | let identifier = "Annotation" 214 | var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) 215 | if annotationView == nil { 216 | annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier) 217 | annotationView!.canShowCallout = true 218 | } else { 219 | annotationView!.annotation = annotation 220 | } 221 | return annotationView 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_airport.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-airport.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icons8-airport-1.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "icons8-airport-2.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_airport.imageset/icons8-airport-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_airport.imageset/icons8-airport-1.png -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_airport.imageset/icons8-airport-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_airport.imageset/icons8-airport-2.png -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_airport.imageset/icons8-airport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_airport.imageset/icons8-airport.png -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_back.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-back.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icons8-back-1.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "icons8-back-2.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_back.imageset/icons8-back-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_back.imageset/icons8-back-1.png -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_back.imageset/icons8-back-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_back.imageset/icons8-back-2.png -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_back.imageset/icons8-back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_back.imageset/icons8-back.png -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_clear.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-multiply.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icons8-multiply-1.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "icons8-multiply-2.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_clear.imageset/icons8-multiply-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_clear.imageset/icons8-multiply-1.png -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_clear.imageset/icons8-multiply-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_clear.imageset/icons8-multiply-2.png -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_clear.imageset/icons8-multiply.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_clear.imageset/icons8-multiply.png -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_coffee.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-coffee-2.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icons8-coffee.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "icons8-coffee-1.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_coffee.imageset/icons8-coffee-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_coffee.imageset/icons8-coffee-1.png -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_coffee.imageset/icons8-coffee-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_coffee.imageset/icons8-coffee-2.png -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_coffee.imageset/icons8-coffee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_coffee.imageset/icons8-coffee.png -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_gas.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-gas_station.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icons8-gas_station-1.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "icons8-gas_station-2.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_gas.imageset/icons8-gas_station-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_gas.imageset/icons8-gas_station-1.png -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_gas.imageset/icons8-gas_station-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_gas.imageset/icons8-gas_station-2.png -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_gas.imageset/icons8-gas_station.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_gas.imageset/icons8-gas_station.png -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_locate.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-near_me.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icons8-near_me-1.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "icons8-near_me-2.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_locate.imageset/icons8-near_me-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_locate.imageset/icons8-near_me-1.png -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_locate.imageset/icons8-near_me-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_locate.imageset/icons8-near_me-2.png -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_locate.imageset/icons8-near_me.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_locate.imageset/icons8-near_me.png -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_location.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-marker.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icons8-marker-1.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "icons8-marker-2.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_location.imageset/icons8-marker-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_location.imageset/icons8-marker-1.png -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_location.imageset/icons8-marker-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_location.imageset/icons8-marker-2.png -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_location.imageset/icons8-marker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_location.imageset/icons8-marker.png -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_parking.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-parking.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icons8-parking-1.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "icons8-parking-2.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_parking.imageset/icons8-parking-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_parking.imageset/icons8-parking-1.png -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_parking.imageset/icons8-parking-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_parking.imageset/icons8-parking-2.png -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_parking.imageset/icons8-parking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_parking.imageset/icons8-parking.png -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_restaurant.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-restaurant.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icons8-restaurant-1.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "icons8-restaurant-2.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_restaurant.imageset/icons8-restaurant-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_restaurant.imageset/icons8-restaurant-1.png -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_restaurant.imageset/icons8-restaurant-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_restaurant.imageset/icons8-restaurant-2.png -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_restaurant.imageset/icons8-restaurant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_restaurant.imageset/icons8-restaurant.png -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_search.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-search.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icons8-search-1.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "icons8-search-2.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_search.imageset/icons8-search-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_search.imageset/icons8-search-1.png -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_search.imageset/icons8-search-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_search.imageset/icons8-search-2.png -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_search.imageset/icons8-search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_search.imageset/icons8-search.png -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_shopping.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-shopping.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icons8-shopping-1.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "icons8-shopping-2.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_shopping.imageset/icons8-shopping-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_shopping.imageset/icons8-shopping-1.png -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_shopping.imageset/icons8-shopping-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_shopping.imageset/icons8-shopping-2.png -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_shopping.imageset/icons8-shopping.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Assets.xcassets/ic_shopping.imageset/icons8-shopping.png -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/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 | -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Fonts/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Fonts/Roboto-Bold.ttf -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/Fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Resources/ResManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ResManager.swift 3 | // MaterialDesignSearchbar 4 | // 5 | // Created by Ho, Tsung Wei on 7/14/19. 6 | // Copyright © 2019 Ho, Tsungwei. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ResManager { 12 | // MARK: - Colors 13 | struct Colors { 14 | static let sand = UIColor(red: 0.94, green: 0.89, blue: 0.77, alpha: 1.0) 15 | } 16 | // MARK: - Font 17 | struct Font { 18 | static let bold = { (size: CGFloat) in 19 | UIFont(name: "Roboto-Bold", size: size) ?? UIFont.systemFont(ofSize: size) 20 | } 21 | static let regular = { (size: CGFloat) in 22 | UIFont(name: "Roboto-Regular", size: size) ?? UIFont.systemFont(ofSize: size) 23 | } 24 | } 25 | /** 26 | Placemark icons. Note that this will not be used until iOS 13. 27 | 28 | - Parameter poi: The point of interest as the input to render specific image. 29 | */ 30 | static let placemarkIcon = { (poi: [String]?) -> UIImage in 31 | guard let poi = poi, poi.count > 0 else { return #imageLiteral(resourceName: "ic_destination") } 32 | switch poi { 33 | case let str where str.contains("cafe"), let str where str.contains("coffee"): 34 | return #imageLiteral(resourceName: "ic_coffee") 35 | case let str where str.contains("gas"): 36 | return #imageLiteral(resourceName: "ic_gas") 37 | case let str where str.contains("parking"): 38 | return #imageLiteral(resourceName: "ic_parking") 39 | case let str where str.contains("airport"): 40 | return #imageLiteral(resourceName: "ic_airport") 41 | case let str where str.contains("restaurant"): 42 | return #imageLiteral(resourceName: "ic_restaurant") 43 | case let str where str.contains("shop"): 44 | return #imageLiteral(resourceName: "ic_shopping") 45 | default: 46 | return #imageLiteral(resourceName: "ic_location") 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Source/AnimHelper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnimHelper.swift 3 | // MaterialDesignSearchbar 4 | // 5 | // Created by Ho, Tsung Wei on 7/11/19. 6 | // Copyright © 2019 Ho, Tsungwei. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AnimHelper { 12 | // Singleton 13 | static let shared = AnimHelper() 14 | public enum ViewOrigin { 15 | case below 16 | case above 17 | case identity 18 | } 19 | /** 20 | Move up the views from specified position to a specified coordinate above its 21 | original position. Show the views after moving. 22 | 23 | - Parameter view: The view to be applied this moving animation. 24 | - Parameter duration: The duration of the animation. 25 | - Parameter completion: The completion handler. 26 | - Parameter fromY: The y coordinate of the views as the starting position. It is set to be equal to the views heights by default. 27 | - Parameter toY: The y coordinate of the views as the ending position. It is set to set to be equal to negative views heights by default. 28 | */ 29 | public func showViewsByMovingUp(_ views: [UIView], duration: TimeInterval = 0.5, completion: ((Bool) -> Void)? = nil, 30 | fromY: CGFloat? = nil, toY: CGFloat? = nil) { 31 | views.forEach({ 32 | let startingY = fromY == nil ? $0.frame.height : fromY! 33 | $0.alpha = 0.0 34 | $0.transform = CGAffineTransform(translationX: $0.transform.tx, y: $0.transform.ty + startingY) 35 | }) 36 | UIView.animate(withDuration: duration, 37 | delay: 0, 38 | usingSpringWithDamping: 0.9, 39 | initialSpringVelocity: 0.15, 40 | options: .curveEaseOut, 41 | animations: { () -> Void in 42 | views.forEach { 43 | let endingY = toY == nil ? -$0.frame.height : toY! 44 | $0.transform = CGAffineTransform(translationX: $0.transform.tx, y: $0.transform.ty + endingY) 45 | $0.alpha = 1.0 46 | } 47 | }, completion: { (finished) in 48 | // Reset the views to their original position 49 | views.forEach { 50 | $0.transform = .identity 51 | } 52 | completion?(finished) 53 | }) 54 | } 55 | /** 56 | Move down the views from specified top position to a specified coordinate below its 57 | original position. Hide the views after moving. 58 | 59 | - Parameter view: The view to be applied this moving animation. 60 | - Parameter duration: The duration of the animation. 61 | - Parameter completion: The completion handler. 62 | - Parameter fromY: The y coordinate of the views as the starting position. It is set to 0 by default, which means views remain their original position. 63 | - Parameter toY: The y coordinate of the views as the ending position. It is set to be equal to the views heights by default. 64 | */ 65 | public func hideViewsByMovingDown(_ views: [UIView], duration: TimeInterval = 0.5, completion: ((Bool) -> Void)? = nil, 66 | fromY: CGFloat? = nil, toY: CGFloat? = nil) { 67 | views.forEach({ 68 | let startingY = fromY == nil ? 0.0 : fromY! 69 | $0.alpha = 1.0 70 | $0.transform = CGAffineTransform(translationX: $0.transform.tx, y: $0.transform.ty + startingY) 71 | }) 72 | UIView.animate(withDuration: duration, 73 | delay: 0, 74 | usingSpringWithDamping: 0.9, 75 | initialSpringVelocity: 0.15, 76 | options: .curveEaseOut, 77 | animations: { () -> Void in 78 | views.forEach { 79 | let endingY = toY == nil ? $0.frame.height : toY! 80 | $0.transform = CGAffineTransform(translationX: $0.transform.tx, y: $0.transform.ty + endingY) 81 | $0.alpha = 0.0 82 | } 83 | }, completion: { (finished) in 84 | // Reset the views to their original position 85 | views.forEach { 86 | $0.transform = .identity 87 | } 88 | completion?(finished) 89 | }) 90 | } 91 | /** 92 | Move down the views from specified top position to a specified coordinate below its 93 | original position. Show the views after moving. 94 | 95 | - Parameter view: The view to be applied this moving animation. 96 | - Parameter duration: The duration of the animation. 97 | - Parameter completion: The completion handler. 98 | - Parameter fromY: The y coordinate of the views as the starting position. It is set to 0 by default, which means views remain their original position. 99 | - Parameter toY: The y coordinate of the views as the ending position. It is set to be equal to the views heights by default. 100 | */ 101 | public func showViewsByMovingDown(_ views: [UIView], duration: TimeInterval = 0.5, completion: ((Bool) -> Void)? = nil, 102 | fromY: CGFloat? = nil, toY: CGFloat? = nil) { 103 | views.forEach { 104 | let startingY = fromY == nil ? -$0.frame.height : fromY! 105 | $0.alpha = 0.0 106 | $0.transform = CGAffineTransform(translationX: $0.transform.tx, y: $0.transform.ty + startingY) 107 | } 108 | UIView.animate(withDuration: duration, 109 | delay: 0, 110 | usingSpringWithDamping: 0.9, 111 | initialSpringVelocity: 0.15, 112 | options: .curveEaseOut, 113 | animations: { () -> Void in 114 | views.forEach { 115 | let endingY = toY == nil ? $0.frame.height : toY! 116 | $0.transform = CGAffineTransform(translationX: $0.transform.tx, y: $0.transform.ty + endingY) 117 | $0.alpha = 1.0 118 | } 119 | }, completion: { (finished) in 120 | // Reset the views to their original position 121 | views.forEach { 122 | $0.transform = .identity 123 | } 124 | completion?(finished) 125 | }) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Source/Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions.swift 3 | // MaterialDesignWidgets 4 | // 5 | // Created by Ho, Tsung Wei on 5/16/19. 6 | // Copyright © 2019 Michael Ho. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreLocation 11 | import MaterialDesignWidgets 12 | 13 | public typealias BtnAction = (() -> Void) 14 | public typealias DidSelectLocation = ((CLPlacemark) -> Void)? 15 | 16 | // MARK: - UIImage 17 | extension UIImage { 18 | /** 19 | Change the color of the image. 20 | 21 | - Parameter color: The color to be set to the UIImage. 22 | 23 | Returns an UIImage with specified color 24 | */ 25 | public func colored(_ color: UIColor?) -> UIImage? { 26 | if let newColor = color { 27 | UIGraphicsBeginImageContextWithOptions(size, false, scale) 28 | // Init context 29 | let context = UIGraphicsGetCurrentContext()! 30 | context.translateBy(x: 0, y: size.height) 31 | context.scaleBy(x: 1.0, y: -1.0) 32 | context.setBlendMode(.normal) 33 | // Init rect 34 | let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height) 35 | context.clip(to: rect, mask: cgImage!) 36 | // Draw color 37 | newColor.setFill() 38 | context.fill(rect) 39 | // Generate new image 40 | let newImage = UIGraphicsGetImageFromCurrentImageContext()! 41 | UIGraphicsEndImageContext() 42 | newImage.accessibilityIdentifier = accessibilityIdentifier 43 | return newImage 44 | } 45 | return self 46 | } 47 | } 48 | // MARK: - UIStackView 49 | extension UIStackView { 50 | /** 51 | Convenient initializer. 52 | 53 | - Parameter arrangedSubviews: all arranged subviews to be put to the stack. 54 | - Parameter axis: The arranged axis of the stack view. 55 | - Parameter distribution: The distribution of the stack view. 56 | - Parameter spacing: The spacing between each view in the stack view. 57 | */ 58 | convenience init(arrangedSubviews: [UIView]? = nil, axis: NSLayoutConstraint.Axis, distribution: UIStackView.Distribution, spacing: CGFloat) { 59 | if let arrangedSubviews = arrangedSubviews { 60 | self.init(arrangedSubviews: arrangedSubviews) 61 | } else { 62 | self.init() 63 | } 64 | (self.axis, self.spacing, self.distribution) = (axis, spacing, distribution) 65 | } 66 | } 67 | // MARK: - NSObject 68 | extension NSObject { 69 | /** 70 | We assume the AppDelegate exists in the entire life cycle of the app. 71 | */ 72 | var appDelegate: AppDelegate { 73 | return (UIApplication.shared.delegate as? AppDelegate).unsafelyUnwrapped 74 | } 75 | } 76 | // MARK: - UIView 77 | extension UIView { 78 | /** 79 | The getter of the height constraint of the view. 80 | */ 81 | var heightConstraint: NSLayoutConstraint? { 82 | get { 83 | return constraints.first(where: { 84 | $0.firstAttribute == .height && $0.relation == .equal 85 | }) 86 | } 87 | } 88 | /** 89 | The getter of the width constraint of the view. 90 | */ 91 | var widthConstraint: NSLayoutConstraint? { 92 | get { 93 | return constraints.first(where: { 94 | $0.firstAttribute == .width && $0.relation == .equal 95 | }) 96 | } 97 | } 98 | /** 99 | Make the specified view (in parameter) to be centered of current view. 100 | 101 | - Parameter view: The view to be positioned to the center of current view. 102 | */ 103 | public func centerSubView(_ view: UIView) { 104 | self.addConstraints([ 105 | NSLayoutConstraint(item: view, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1.0, constant: 0.0), 106 | NSLayoutConstraint(item: view, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1.0, constant: 0.0)] 107 | ) 108 | } 109 | /** 110 | Add subviews and make it prepared for AutoLayout. 111 | 112 | - Parameter views: The views to be added as subviews of current view. 113 | */ 114 | public func addSubViews(_ views: [UIView]) { 115 | views.forEach({ 116 | self.addSubview($0) 117 | $0.translatesAutoresizingMaskIntoConstraints = false 118 | }) 119 | } 120 | /** 121 | Set layout constraints of top, bottom, left, and right bounds. 122 | 123 | - Parameter top: The view that the current view references as the top view. 124 | - Parameter tConst: The constant to be applied to the top constraint. 125 | - Parameter bottom: The view that the current view references as the bottom view. 126 | - Parameter bConst: The constant to be applied to the bottom constraint. 127 | - Parameter left: The view that the current view references as the left view. 128 | - Parameter lConst: The constant to be applied to the left constraint. 129 | - Parameter right: The view that the current view references as the right view. 130 | - Parameter rConst: The constant to be applied to the right constraint. 131 | */ 132 | public func setConstraintsToView(top: UIView? = nil, tConst: CGFloat = 0, 133 | bottom: UIView? = nil, bConst: CGFloat = 0, 134 | left: UIView? = nil, lConst: CGFloat = 0, 135 | right: UIView? = nil, rConst: CGFloat = 0) { 136 | guard let suView = self.superview else { return } 137 | // Set top constraints if the view is specified. 138 | if let top = top { 139 | suView.addConstraint( 140 | NSLayoutConstraint(item: self, attribute: .top, 141 | relatedBy: .equal, 142 | toItem: top, attribute: .top, 143 | multiplier: 1.0, constant: tConst) 144 | ) 145 | } 146 | // Set bottom constraints if the view is specified. 147 | if let bottom = bottom { 148 | suView.addConstraint( 149 | NSLayoutConstraint(item: self, attribute: .bottom, 150 | relatedBy: .equal, 151 | toItem: bottom, attribute: .bottom, 152 | multiplier: 1.0, constant: bConst) 153 | ) 154 | } 155 | // Set left constraints if the view is specified. 156 | if let left = left { 157 | suView.addConstraint( 158 | NSLayoutConstraint(item: self, attribute: .left, 159 | relatedBy: .equal, 160 | toItem: left, attribute: .left, 161 | multiplier: 1.0, constant: lConst) 162 | ) 163 | } 164 | // Set right constraints if the view is specified. 165 | if let right = right { 166 | suView.addConstraint( 167 | NSLayoutConstraint(item: self, attribute: .right, 168 | relatedBy: .equal, 169 | toItem: right, attribute: .right, 170 | multiplier: 1.0, constant: rConst) 171 | ) 172 | } 173 | } 174 | /** 175 | The setter of the height constraint of the view. 176 | 177 | - Parameter height: The height as constant in CGFloat to set to layout constraint. 178 | */ 179 | public func setHeightConstraint(_ height: CGFloat) { 180 | if self.heightConstraint != nil { 181 | self.heightConstraint?.isActive = false 182 | } 183 | NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: height).isActive = true 184 | } 185 | /** 186 | The setter of the width constraint of the view. 187 | 188 | - Parameter width: The width as constant in CGFloat to set to layout constraint. 189 | */ 190 | public func setWidthConstraint(_ width: CGFloat) { 191 | if self.widthConstraint != nil { 192 | self.widthConstraint?.isActive = false 193 | } 194 | NSLayoutConstraint(item: self, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: width).isActive = true 195 | } 196 | } 197 | // MARK: - UILabel 198 | extension UILabel { 199 | /** 200 | Convenience initializers of UILable for app-wide use. 201 | 202 | - Parameter title: The title of the label. 203 | - Parameter size: The font size. 204 | - Parameter color: The text color. 205 | - Parameter bold: The flag to decide if use bold font. 206 | - Parameter lines: The number of lines of the label. 207 | - Parameter aligment: The text alignment of the the label. 208 | */ 209 | public convenience init(title: String, size: CGFloat, color: UIColor, bold: Bool = false, lines: Int, aligment: NSTextAlignment) { 210 | self.init() 211 | self.text = title 212 | self.font = bold ? ResManager.Font.bold(size) : ResManager.Font.regular(size) 213 | self.textColor = color 214 | self.numberOfLines = lines 215 | self.textAlignment = aligment 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Source/SearchResultsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchResultsView.swift 3 | // MaterialDesignSearchbar 4 | // 5 | // Created by Ho, Tsung Wei on 7/12/19. 6 | // Copyright © 2019 Ho, Tsungwei. All rights reserved. 7 | // 8 | 9 | import MaterialDesignWidgets 10 | import CoreLocation 11 | 12 | /** 13 | The state of the tableView used in SearchResultsView. 14 | */ 15 | enum State { 16 | /// When the table is loading. 17 | case loading 18 | /// When the loading is done, show data. 19 | /// - placemarks shown in the tableView. 20 | case populated([CLPlacemark]) 21 | /// When there's no results to show. 22 | case empty 23 | /// When the table is loading. 24 | /// - The error object with error messages. 25 | case error(Error) 26 | /** 27 | The stored placemark data. 28 | */ 29 | var placemarks: [CLPlacemark] { 30 | switch self { 31 | case .populated(let placemarks): 32 | return placemarks 33 | default: 34 | return [] 35 | } 36 | } 37 | } 38 | 39 | class SearchResultsView: UIView { 40 | // MARK: - UI widgets 41 | private var tableView: UITableView! 42 | private var activityIndicator: MaterialLoadingIndicator! 43 | private var loadingView: UIView! 44 | private var emptyView: UIView! 45 | private var noResultLabel: UILabel! 46 | private var errorView: UIView! 47 | private var errorLabel: UILabel! 48 | // MARK: - Private properties 49 | private var cellId = "ResultCell" 50 | private var didSelectAction: DidSelectLocation? 51 | // MARK: - Public properties 52 | public var rowHeight: CGFloat = 60.0 53 | public var isScrolling = false 54 | /** 55 | The current state of the tableView. Reload the tableView every time the state is changed. 56 | */ 57 | var state = State.loading { 58 | didSet { 59 | DispatchQueue.main.async { [weak self] in 60 | guard let self = self else { return } 61 | self.setFooterView() 62 | // Reload table with animations 63 | let range = NSMakeRange(0, self.tableView.numberOfSections) 64 | let sections = NSIndexSet(indexesIn: range) 65 | self.tableView.reloadSections(sections as IndexSet, with: .automatic) 66 | } 67 | } 68 | } 69 | // Init 70 | override init(frame: CGRect) { 71 | super.init(frame: frame) 72 | initUI() 73 | } 74 | /** 75 | Convenience initializer for app use. 76 | 77 | - Parameter didSelectAction: Handle when user select an item in the search results view. 78 | */ 79 | convenience init(didSelectAction: DidSelectLocation) { 80 | self.init() 81 | self.didSelectAction = didSelectAction 82 | } 83 | /** 84 | Init UI. 85 | */ 86 | private func initUI() { 87 | self.setCornerBorder() 88 | prepareTableView() 89 | // Set up loading view 90 | loadingView = UIView(frame: CGRect(x: 0, y: 0, width: self.frame.width, height: self.rowHeight)) 91 | activityIndicator = MaterialLoadingIndicator() 92 | loadingView.addSubViews([activityIndicator]) 93 | // Set up empty view 94 | emptyView = UIView(frame: CGRect(x: 0, y: 0, width: self.frame.width, height: self.rowHeight)) 95 | noResultLabel = UILabel(title: "No results! Try searching for something else.", size: 17.0, color: .gray, lines: 3, aligment: .center) 96 | emptyView.addSubViews([noResultLabel]) 97 | // Set up error view 98 | errorView = UIView(frame: CGRect(x: 0, y: 0, width: self.frame.width, height: self.rowHeight)) 99 | errorLabel = UILabel(title: "There is an error.", size: 17.0, color: .gray, lines: 3, aligment: .center) 100 | errorView.addSubViews([errorLabel]) 101 | } 102 | // layoutSubviews 103 | override func layoutSubviews() { 104 | super.layoutSubviews() 105 | loadingView.centerSubView(activityIndicator) 106 | errorLabel.leftAnchor.constraint(equalTo: errorView.leftAnchor).isActive = true 107 | errorLabel.rightAnchor.constraint(equalTo: errorView.rightAnchor).isActive = true 108 | errorView.centerSubView(errorLabel) 109 | noResultLabel.leftAnchor.constraint(equalTo: emptyView.leftAnchor).isActive = true 110 | noResultLabel.rightAnchor.constraint(equalTo: emptyView.rightAnchor).isActive = true 111 | emptyView.centerSubView(noResultLabel) 112 | tableView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true 113 | tableView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true 114 | tableView.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true 115 | tableView.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true 116 | } 117 | /** 118 | Set up tableView and add to parent view. 119 | */ 120 | private func prepareTableView() { 121 | tableView = UITableView() 122 | (tableView.delegate, tableView.dataSource, tableView.separatorStyle, tableView.keyboardDismissMode) = (self, self, .none, .onDrag) 123 | tableView.register(ResultCell.self, forCellReuseIdentifier: cellId) 124 | tableView.isUserInteractionEnabled = true 125 | tableView.canCancelContentTouches = false 126 | tableView.backgroundColor = self.appDelegate.uiStyle == .dark ? .darkGray : .white 127 | self.addSubViews([tableView]) 128 | } 129 | /** 130 | Update current data source. 131 | 132 | - Parameter newPlacemarks: New data source. 133 | - Parameter error: Error occurred while fetching data. 134 | */ 135 | func update(newPlacemarks: [CLPlacemark]?, error: Error?) { 136 | if let error = error { 137 | state = .error(error) 138 | return 139 | } 140 | guard let newPlacemarks = newPlacemarks, !newPlacemarks.isEmpty else { 141 | state = .empty 142 | return 143 | } 144 | state = .populated(newPlacemarks) 145 | } 146 | /** 147 | Change footer view based on current state. 148 | */ 149 | func setFooterView() { 150 | switch state { 151 | case .error(let error): 152 | errorLabel.text = error.localizedDescription 153 | tableView.tableFooterView = errorView 154 | case .loading: 155 | activityIndicator.startAnimating() 156 | tableView.tableFooterView = loadingView 157 | case .empty: 158 | tableView.tableFooterView = emptyView 159 | case .populated: 160 | tableView.tableFooterView = nil 161 | } 162 | } 163 | // Required init 164 | required init?(coder aDecoder: NSCoder) { 165 | super.init(coder: aDecoder) 166 | } 167 | } 168 | // MARK: - UITableViewDataSource, UITableViewDelegate, UIScrollViewDelegate 169 | extension SearchResultsView: UITableViewDataSource, UITableViewDelegate { 170 | 171 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 172 | return min(state.placemarks.count, 10) 173 | } 174 | 175 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 176 | guard let cell = tableView.dequeueReusableCell(withIdentifier: cellId) as? ResultCell else { 177 | return UITableViewCell() 178 | } 179 | cell.configure(state.placemarks[indexPath.row]) 180 | return cell 181 | } 182 | // didSelectRowAt 183 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 184 | if let onSelect = self.didSelectAction, indexPath.row < state.placemarks.count { 185 | onSelect!(state.placemarks[indexPath.row]) 186 | } 187 | tableView.deselectRow(at: indexPath, animated: true) 188 | } 189 | 190 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 191 | return rowHeight 192 | } 193 | 194 | func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { 195 | isScrolling = true 196 | } 197 | 198 | func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { 199 | isScrolling = false 200 | } 201 | } 202 | 203 | class ResultCell: MaterialTableViewCell { 204 | // MARK: - UI widgets 205 | private var icon: UIImageView! 206 | private var caption: UILabel! 207 | private var title: UILabel! 208 | private var subTitle: UILabel! 209 | private var rightStack: UIStackView! 210 | private var leftStack: UIStackView! 211 | public var foregroundColor: UIColor = .darkGray 212 | // Init 213 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 214 | super.init(style: style, reuseIdentifier: reuseIdentifier) 215 | // Set up colors to respond dark mode 216 | self.backgroundColor = appDelegate.uiStyle == .light ? .white : .darkGray 217 | setCornerBorder() 218 | foregroundColor = appDelegate.uiStyle == .light ? .gray : .white 219 | // Set up UI 220 | icon = UIImageView(image: #imageLiteral(resourceName: "ic_location").colored(foregroundColor)) 221 | title = UILabel(title: "", size: 16.0, color: foregroundColor, lines: 1, aligment: .left) 222 | subTitle = UILabel(title: "", size: 14.0, color: foregroundColor, lines: 1, aligment: .left) 223 | caption = UILabel(title: "", size: 12.0, color: foregroundColor, lines: 1, aligment: .left) 224 | caption.adjustsFontSizeToFitWidth = true 225 | rightStack = UIStackView(arrangedSubviews: [title, subTitle], axis: .vertical, distribution: .fillProportionally, spacing: 0.0) 226 | leftStack = UIStackView(arrangedSubviews: [icon, caption], axis: .vertical, distribution: .fillProportionally, spacing: 0.0) 227 | leftStack.alignment = .center 228 | self.addSubViews([leftStack, rightStack]) 229 | } 230 | /** 231 | Set the content of the cell. 232 | 233 | - Parameter currentPlacemark: The data object that contains the data to be displayed. 234 | */ 235 | func configure(_ currentPlacemark: CLPlacemark) { 236 | title.text = currentPlacemark.name 237 | subTitle.text = parseAddress(currentPlacemark) 238 | // Will be used in until point of interest category information is released in iOS 13 239 | // icon.image = ResManager.placemarkIcon(currentPlacemark.areasOfInterest).colored(.gray) 240 | guard 241 | let userLoc = appDelegate.latestLocation, 242 | let placemarkLocation = currentPlacemark.location 243 | else { 244 | return 245 | } 246 | let distance = round(userLoc.distance(from: placemarkLocation) / 1600.0 * 10) / 10 247 | self.caption.text = "\(distance) mi" 248 | } 249 | /** 250 | Parse the address from placemark. 251 | Refer to https://www.thorntech.com/2016/01/how-to-search-for-location-using-apples-mapkit/ 252 | 253 | - Parameter placemark: The placemark data object. 254 | 255 | - Returns: A string represents the address of the placemark. 256 | */ 257 | private func parseAddress(_ placemark: CLPlacemark) -> String { 258 | let firstSpace = (placemark.subThoroughfare != nil && placemark.thoroughfare != nil) ? " " : "" 259 | let comma = (placemark.subThoroughfare != nil || placemark.thoroughfare != nil) && (placemark.subAdministrativeArea != nil || placemark.administrativeArea != nil) ? ", " : "" 260 | let secondSpace = (placemark.subAdministrativeArea != nil && placemark.administrativeArea != nil) ? " " : "" 261 | let addressLine = String( 262 | format:"%@%@%@%@%@%@%@", 263 | // street number 264 | placemark.subThoroughfare ?? "", 265 | firstSpace, 266 | // street name 267 | placemark.thoroughfare ?? "", 268 | comma, 269 | // city 270 | placemark.locality ?? "", 271 | secondSpace, 272 | // state 273 | placemark.administrativeArea ?? "" 274 | ) 275 | return addressLine 276 | } 277 | // layoutSubviews 278 | override func layoutSubviews() { 279 | super.layoutSubviews() 280 | // Set up layouts 281 | leftStack.setConstraintsToView(left: self, lConst: 0.02 * self.frame.width) 282 | rightStack.setConstraintsToView(right: self) 283 | self.addConstraints([ 284 | NSLayoutConstraint(item: leftStack as Any, attribute: .centerY, 285 | relatedBy: .equal, 286 | toItem: self, attribute: .centerY, 287 | multiplier: 1.0, constant: 0.0), 288 | NSLayoutConstraint(item: rightStack as Any, attribute: .centerY, 289 | relatedBy: .equal, 290 | toItem: self, attribute: .centerY, 291 | multiplier: 1.0, constant: 0.0), 292 | NSLayoutConstraint(item: rightStack as Any, attribute: .left, 293 | relatedBy: .equal, 294 | toItem: leftStack, attribute: .right, 295 | multiplier: 1.0, constant: 0.03*self.frame.width) 296 | ]) 297 | leftStack.setWidthConstraint(self.frame.height) 298 | icon.setWidthConstraint(0.4*self.frame.height) 299 | self.layoutIfNeeded() 300 | } 301 | // Required init 302 | required init?(coder aDecoder: NSCoder) { 303 | super.init(coder: aDecoder) 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchUI/Source/Searchbar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Searchbar.swift 3 | // MaterialDesignSearchbar 4 | // 5 | // Created by Ho, Tsungwei on 7/11/19. 6 | // Copyright © 2019 Ho, Tsungwei. All rights reserved. 7 | // 8 | 9 | import MaterialDesignWidgets 10 | 11 | class Searchbar: UIView, UITextFieldDelegate { 12 | // MARK: - UI widgets 13 | open var btnLeft: MaterialButton! 14 | open var btnRight: MaterialButton! 15 | open var textInput: MaterialTextField! 16 | // Delegation 17 | open weak var delegate: SearchbarDelegate? 18 | // MARK: - Public properties 19 | public var inputTimeout = 0.5 20 | public var cornerRadius: CGFloat = 10.0 21 | public var borderWidth: CGFloat = 1.0 22 | public var borderColor: UIColor = .lightGray 23 | public var searchbarHint = "Enter destination" 24 | public var foregroundColor: UIColor = .darkGray 25 | public var imgLeftBtn: UIImage = #imageLiteral(resourceName: "ic_search") 26 | // MARK: - Private properties 27 | private var stackView: UIStackView! 28 | private var onStartSearch: ((Bool) -> Void)? 29 | private var onClearInput: BtnAction? 30 | private var startSearch: DispatchWorkItem? 31 | private let animHplr = AnimHelper.shared 32 | // Init 33 | private override init(frame: CGRect) { 34 | super.init(frame: frame) 35 | initUI() 36 | btnLeft.addTarget(self, action: #selector(tapLeftBtn), for: .touchUpInside) 37 | btnRight.addTarget(self, action: #selector(tapRightBtn), for: .touchUpInside) 38 | textInput.addTarget(self, action: #selector(textFieldDidChange(_ :)), for: .editingChanged) 39 | } 40 | /** 41 | Convenience initializer. 42 | 43 | - Parameter onTapLeft: 44 | - Parameter onTapRight: 45 | - Parameter delegate: 46 | */ 47 | public convenience init(onStartSearch: ((Bool) -> Void)?, onClearInput: BtnAction?, delegate: SearchbarDelegate) { 48 | // We use zero here since the size of the view is handled by AutoLayout. 49 | self.init(frame: .zero) 50 | self.delegate = delegate 51 | self.onStartSearch = onStartSearch 52 | self.onClearInput = onClearInput 53 | } 54 | /** 55 | Init UI. 56 | */ 57 | private func initUI() { 58 | self.backgroundColor = self.appDelegate.uiStyle == .dark ? .darkGray : .white 59 | foregroundColor = self.appDelegate.uiStyle == .dark ? .white : .darkGray 60 | // Set corner border 61 | self.layer.borderColor = borderColor.cgColor 62 | self.layer.borderWidth = borderWidth 63 | self.layer.cornerRadius = cornerRadius 64 | self.clipsToBounds = true 65 | // Set up textField 66 | textInput = MaterialTextField(placeholder: searchbarHint, textColor: foregroundColor, bgColor: self.backgroundColor ?? .white, font: ResManager.Font.regular(16.0), delegate: self) 67 | textInput.autocorrectionType = .no 68 | textInput.returnKeyType = .search 69 | textInput.enablesReturnKeyAutomatically = true 70 | // Set up buttons 71 | btnLeft = MaterialButton(icon: imgLeftBtn.colored(foregroundColor), bgColor: self.backgroundColor ?? .white) 72 | btnRight = MaterialButton(icon: #imageLiteral(resourceName: "ic_clear").colored(foregroundColor), bgColor: self.backgroundColor ?? .white) 73 | stackView = UIStackView(arrangedSubviews: [btnLeft, textInput, btnRight], axis: .horizontal, distribution: .fillProportionally, spacing: 0.0) 74 | self.addSubViews([stackView]) 75 | setBtnStates(hasInput: false, isSearching: false) 76 | } 77 | 78 | override func layoutSubviews() { 79 | super.layoutSubviews() 80 | stackView.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true 81 | stackView.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true 82 | stackView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true 83 | stackView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true 84 | btnLeft.widthAnchor.constraint(equalTo: stackView.heightAnchor, multiplier: 0.8).isActive = true 85 | btnRight.widthAnchor.constraint(equalTo: stackView.heightAnchor, multiplier: 0.8).isActive = true 86 | } 87 | 88 | @objc private func tapLeftBtn() { 89 | if btnLeft.tag == 1 { 90 | textInput.text = "" 91 | textFieldDidEndEditing(textInput) 92 | onStartSearch?(false) 93 | } else { 94 | textInput.becomeFirstResponder() 95 | onStartSearch?(true) 96 | } 97 | } 98 | 99 | @objc private func tapRightBtn() { 100 | textInput.text = "" 101 | btnRight.isHidden = true 102 | onClearInput?() 103 | } 104 | // MARK: UITextFieldDelegate 105 | @objc private func textFieldDidChange(_ textField: UITextField) { 106 | startSearch?.cancel() 107 | textField.text = textField.text?.trimmingCharacters(in: .whitespaces).count == 0 ? "" : textField.text 108 | setBtnStates(hasInput: textField.text?.count != 0, isSearching: true) 109 | startSearch = DispatchWorkItem { [weak self] in 110 | guard let self = self, let startSearch = self.startSearch, !startSearch.isCancelled else { return } 111 | self.delegate?.searchbarTextDidChange(textField) 112 | } 113 | guard let search = self.startSearch else { return } 114 | DispatchQueue.main.asyncAfter(deadline: .now() + inputTimeout, execute: search) 115 | } 116 | // textFieldDidBeginEditing 117 | func textFieldDidBeginEditing(_ textField: UITextField) { 118 | textField.text = textField.text?.trimmingCharacters(in: .whitespaces).count == 0 ? "" : textField.text 119 | setBtnStates(hasInput: textField.text?.count != 0, isSearching: true) 120 | delegate?.textFieldDidBeginEditing(textField) 121 | } 122 | // textFieldDidEndEditing 123 | func textFieldDidEndEditing(_ textField: UITextField) { 124 | textField.text = textField.text?.trimmingCharacters(in: .whitespaces).count == 0 ? "" : textField.text 125 | delegate?.textFieldDidEndEditing(textField) 126 | } 127 | // textFieldShouldReturn 128 | func textFieldShouldReturn(_ textField: UITextField) -> Bool { 129 | textField.text = textField.text?.trimmingCharacters(in: .whitespaces).count == 0 ? "" : textField.text 130 | setBtnStates(hasInput: textField.text?.count != 0, isSearching: false) 131 | return delegate?.searchbarTextShouldReturn(textField) ?? true 132 | } 133 | // Required init 134 | required init?(coder aDecoder: NSCoder) { 135 | super.init(coder: aDecoder) 136 | } 137 | /** 138 | Change button icons in search bar based on current state. 139 | 140 | - Parameter hasInput: The flag indicates if the search bar has text input. 141 | - Parameter isSearching: The flag indicates if the search bar is in search state. 142 | */ 143 | open func setBtnStates(hasInput: Bool, isSearching: Bool) { 144 | btnRight.isHidden = !hasInput 145 | switchLeftBtn(isSearching) 146 | } 147 | /** 148 | Change the left button state and animate the button icon. 149 | 150 | - Parameter isSearching: The flag to indicate that if the search bar is in searching state. 151 | */ 152 | private func switchLeftBtn(_ isSearching: Bool) { 153 | guard btnLeft.tag != (isSearching ? 1 : 0) else { return } 154 | btnLeft.tag = isSearching ? 1 : 0 155 | btnLeft.setImage(isSearching ? #imageLiteral(resourceName: "ic_back").colored(foregroundColor)! : imgLeftBtn.colored(foregroundColor)!) 156 | isSearching 157 | ? animHplr.showViewsByMovingUp([btnLeft]) : animHplr.showViewsByMovingDown([btnLeft]) 158 | if !isSearching { 159 | textInput.resignFirstResponder() 160 | } 161 | } 162 | // deinit 163 | deinit { 164 | NotificationCenter.default.removeObserver(self) 165 | textInput.removeTarget(self, action: #selector(textFieldDidChange), for: .editingChanged) 166 | btnRight.removeTarget(self, action: #selector(tapRightBtn), for: .touchUpInside) 167 | } 168 | } 169 | // MARK: - SearchbarDelegate 170 | protocol SearchbarDelegate: UITextFieldDelegate { 171 | func searchbarTextDidChange(_ textField: UITextField) 172 | func textFieldDidBeginEditing(_ textField: UITextField) 173 | func textFieldDidEndEditing(_ textField: UITextField) 174 | func searchbarTextShouldReturn(_ textField: UITextField) -> Bool 175 | } 176 | -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchbarUITests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /MaterialDesignSearchUI/MaterialDesignSearchbarUITests/MaterialDesignSearchbarUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MaterialDesignSearchbarUITests.swift 3 | // MaterialDesignSearchbarUITests 4 | // 5 | // Created by Ho, Tsung Wei on 7/11/19. 6 | // Copyright © 2019 Ho, Tsungwei. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class MaterialDesignSearchbarUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | 16 | // In UI tests it is usually best to stop immediately when a failure occurs. 17 | continueAfterFailure = false 18 | 19 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 20 | XCUIApplication().launch() 21 | 22 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 23 | } 24 | 25 | override func tearDown() { 26 | // Put teardown code here. This method is called after the invocation of each test method in the class. 27 | } 28 | 29 | func testExample() { 30 | // Use recording to get started writing UI tests. 31 | // Use XCTAssert and related functions to verify your tests produce the correct results. 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /MaterialDesignSearchUI/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | platform :ios, '11.0' 3 | 4 | target 'MaterialDesignSearchUI' do 5 | # Comment the next line if you don't want to use dynamic frameworks 6 | use_frameworks! 7 | 8 | # Pods for MaterialDesignSearchUI 9 | pod 'MaterialDesignWidgets', :path => '~/Documents/Github/material-design-widgets-lite-ios/MaterialDesignWidgets' 10 | 11 | target 'MaterialDesignSearchUIUITests' do 12 | # Pods for testing 13 | end 14 | 15 | end 16 | -------------------------------------------------------------------------------- /MaterialDesignSearchUI/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - MaterialDesignWidgets (0.1.1) 3 | 4 | DEPENDENCIES: 5 | - MaterialDesignWidgets (from `~/Documents/Github/material-design-widgets-lite-ios/MaterialDesignWidgets`) 6 | 7 | EXTERNAL SOURCES: 8 | MaterialDesignWidgets: 9 | :path: "~/Documents/Github/material-design-widgets-lite-ios/MaterialDesignWidgets" 10 | 11 | SPEC CHECKSUMS: 12 | MaterialDesignWidgets: 6d02d3fbba2fa89f6e773983346ad2187d7ef8ff 13 | 14 | PODFILE CHECKSUM: c712e0ad8fc1e413d3ae3226fd3b0b90a877ad97 15 | 16 | COCOAPODS: 1.9.2 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Material Design Search UI iOS 2 | Material design styled search UI consists of search bar and search results view. 3 | 4 | [![Generic badge](https://img.shields.io/badge/Swift-5.0-orange.svg)](https://shields.io/) [![Generic badge](https://img.shields.io/badge/iOS-11.0+-blue.svg)](https://shields.io/) [![Generic badge](https://img.shields.io/badge/Version-0.1.0-orange.svg)](https://shields.io/) [![Generic badge](https://img.shields.io/badge/platform-ios-green.svg)](https://shields.io/) 5 | 6 | overview 7 | 8 | You may see the following [Medium](https://medium.com/) articles for detailed explanation of developing these UI widgets. 9 | 10 | - [Create Material Design Search Bar in iOS — Search UI Part 1/3](https://medium.com/swlh/create-material-design-search-bar-in-ios-search-ui-part-1-3-dfb905de6b01) 11 | - [Create Material Design Search Results View in iOS — Search UI Part 2/3](https://medium.com/swlh/create-material-design-search-results-view-in-ios-search-ui-part-2-3-21c43f0617c) 12 | - [Create Material Design Search UI in iOS — Search UI Part 3/3](https://medium.com/@twho/create-material-design-search-ui-in-ios-search-ui-part-3-3-a367349f5227) 13 | 14 | ## Key Features 15 | - A Material Design search bar with delegation method for view controller use. 16 | - A Material Design and enum-driven search results view that clearly defines results view states. 17 | - Widget classes are made to be **open**, which gives you flexibility to create your own. 18 | - A simple location searching application using the above two widgets is included in the project. 19 | 20 | ## Requirements 21 | - Swift 5.0 22 | - iOS 11.0+ 23 | 24 | ## Usage 25 | ### Search bar 26 | #### Declaration 27 | ```swift 28 | var searchbar: Searchbar! 29 | searchbar = Searchbar( 30 | onStartSearch: { 31 | // Your implementation 32 | }, 33 | onClearInput: { 34 | // Your implementation 35 | }, 36 | delegate: self 37 | ) 38 | ``` 39 | #### Delegate methods 40 | ```swift 41 | extension MainViewController: SearchbarDelegate { 42 | 43 | func searchbarTextDidChange(_ textField: UITextField) { 44 | 45 | } 46 | 47 | func textFieldDidBeginEditing(_ textField: UITextField) { 48 | 49 | } 50 | 51 | func textFieldDidEndEditing(_ textField: UITextField) { 52 | 53 | } 54 | 55 | func searchbarTextShouldReturn(_ textField: UITextField) -> Bool { 56 | return true 57 | } 58 | } 59 | ``` 60 | button 61 | 62 | ### Search results view 63 | #### Declaration 64 | ```swift 65 | var searchResultsView: SearchResultsView! 66 | searchResultsView = SearchResultsView(didSelectAction: { 67 | // Your implementation 68 | }) 69 | ``` 70 | button 71 | 72 | #### Load data into results view 73 | ```swift 74 | searchResultsView.update(newPlacemarks: placemarks, error: error) 75 | ``` 76 | 77 | Note: I use CLPlacemark here, you can change to whatever data type you'd like to use in your app. 78 | 79 | ## Credits 80 | * [Material Design](https://material.io/design/) 81 | * [Le Van Nghia](https://github.com/sharad-paghadal/MaterialKit/tree/master/Source) 82 | * [Icons8](https://icons8.com/) 83 | * [Robert Chen](https://www.thorntech.com/2016/01/how-to-search-for-location-using-apples-mapkit/) 84 | -------------------------------------------------------------------------------- /gif/finalapp.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/gif/finalapp.gif -------------------------------------------------------------------------------- /gif/issue.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/gif/issue.gif -------------------------------------------------------------------------------- /gif/searchbar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/gif/searchbar.gif -------------------------------------------------------------------------------- /gif/searchresultsview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twho/material-design-search-ui-ios/0fdce479e3dbd1b71001f20fda3ec0191bcc86d0/gif/searchresultsview.gif --------------------------------------------------------------------------------