├── .gitignore ├── Interview.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── Interview ├── AppDelegate.swift ├── Info.plist ├── Resources │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ └── Base.lproj │ │ └── LaunchScreen.storyboard ├── SceneDelegate.swift └── Scenes │ └── ListContacts │ ├── Models │ └── Contact.swift │ ├── Services │ └── ListContactsService.swift │ ├── ViewModels │ └── ListContactsViewModel.swift │ └── Views │ ├── ContactCell.swift │ └── ListContactsViewController.swift ├── InterviewTests ├── Info.plist └── Scenes │ └── ListContacts │ ├── Service │ └── ListContactServiceTests.swift │ └── ViewModel │ └── ListContactViewModelTests.swift └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | -------------------------------------------------------------------------------- /Interview.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 6AB24DC023EB546D00530BA5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AB24DBF23EB546D00530BA5 /* AppDelegate.swift */; }; 11 | 6AB24DC223EB546D00530BA5 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AB24DC123EB546D00530BA5 /* SceneDelegate.swift */; }; 12 | 6AB24DC923EB546F00530BA5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6AB24DC823EB546F00530BA5 /* Assets.xcassets */; }; 13 | 6AB24DCC23EB546F00530BA5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6AB24DCA23EB546F00530BA5 /* LaunchScreen.storyboard */; }; 14 | 6AB24DEA23EB54E800530BA5 /* ListContactsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AB24DE923EB54E800530BA5 /* ListContactsViewController.swift */; }; 15 | 6AB24DEF23EB552100530BA5 /* Contact.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AB24DEE23EB552100530BA5 /* Contact.swift */; }; 16 | 6AB24DF223EB554300530BA5 /* ListContactsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AB24DF123EB554300530BA5 /* ListContactsViewModel.swift */; }; 17 | 6AB24DF523EB556700530BA5 /* ListContactsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AB24DF423EB556700530BA5 /* ListContactsService.swift */; }; 18 | 6AB24DF823EB558D00530BA5 /* ContactCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AB24DF723EB558D00530BA5 /* ContactCell.swift */; }; 19 | 6ABE2F9024CEF691009D7593 /* ListContactServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ABE2F8F24CEF691009D7593 /* ListContactServiceTests.swift */; }; 20 | B163815028775CAD0096BF33 /* ListContactViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B163814F28775CAD0096BF33 /* ListContactViewModelTests.swift */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXContainerItemProxy section */ 24 | 6AB24DD323EB546F00530BA5 /* PBXContainerItemProxy */ = { 25 | isa = PBXContainerItemProxy; 26 | containerPortal = 6AB24DB423EB546D00530BA5 /* Project object */; 27 | proxyType = 1; 28 | remoteGlobalIDString = 6AB24DBB23EB546D00530BA5; 29 | remoteInfo = Interview; 30 | }; 31 | /* End PBXContainerItemProxy section */ 32 | 33 | /* Begin PBXFileReference section */ 34 | 6AB24DBC23EB546D00530BA5 /* Interview.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Interview.app; sourceTree = BUILT_PRODUCTS_DIR; }; 35 | 6AB24DBF23EB546D00530BA5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 36 | 6AB24DC123EB546D00530BA5 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 37 | 6AB24DC823EB546F00530BA5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 38 | 6AB24DCB23EB546F00530BA5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 39 | 6AB24DCD23EB546F00530BA5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 40 | 6AB24DD223EB546F00530BA5 /* InterviewTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = InterviewTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | 6AB24DD823EB546F00530BA5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 42 | 6AB24DE923EB54E800530BA5 /* ListContactsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListContactsViewController.swift; sourceTree = ""; }; 43 | 6AB24DEE23EB552100530BA5 /* Contact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Contact.swift; sourceTree = ""; }; 44 | 6AB24DF123EB554300530BA5 /* ListContactsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListContactsViewModel.swift; sourceTree = ""; }; 45 | 6AB24DF423EB556700530BA5 /* ListContactsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListContactsService.swift; sourceTree = ""; }; 46 | 6AB24DF723EB558D00530BA5 /* ContactCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactCell.swift; sourceTree = ""; }; 47 | 6ABE2F8F24CEF691009D7593 /* ListContactServiceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListContactServiceTests.swift; sourceTree = ""; }; 48 | B163814F28775CAD0096BF33 /* ListContactViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListContactViewModelTests.swift; sourceTree = ""; }; 49 | /* End PBXFileReference section */ 50 | 51 | /* Begin PBXFrameworksBuildPhase section */ 52 | 6AB24DB923EB546D00530BA5 /* Frameworks */ = { 53 | isa = PBXFrameworksBuildPhase; 54 | buildActionMask = 2147483647; 55 | files = ( 56 | ); 57 | runOnlyForDeploymentPostprocessing = 0; 58 | }; 59 | 6AB24DCF23EB546F00530BA5 /* Frameworks */ = { 60 | isa = PBXFrameworksBuildPhase; 61 | buildActionMask = 2147483647; 62 | files = ( 63 | ); 64 | runOnlyForDeploymentPostprocessing = 0; 65 | }; 66 | /* End PBXFrameworksBuildPhase section */ 67 | 68 | /* Begin PBXGroup section */ 69 | 6AB24DB323EB546D00530BA5 = { 70 | isa = PBXGroup; 71 | children = ( 72 | 6AB24DBE23EB546D00530BA5 /* Interview */, 73 | 6AB24DD523EB546F00530BA5 /* InterviewTests */, 74 | 6AB24DBD23EB546D00530BA5 /* Products */, 75 | ); 76 | sourceTree = ""; 77 | }; 78 | 6AB24DBD23EB546D00530BA5 /* Products */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | 6AB24DBC23EB546D00530BA5 /* Interview.app */, 82 | 6AB24DD223EB546F00530BA5 /* InterviewTests.xctest */, 83 | ); 84 | name = Products; 85 | sourceTree = ""; 86 | }; 87 | 6AB24DBE23EB546D00530BA5 /* Interview */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | 6AB24DF923EB55D400530BA5 /* Resources */, 91 | 6AB24DEB23EB54F500530BA5 /* Scenes */, 92 | 6AB24DBF23EB546D00530BA5 /* AppDelegate.swift */, 93 | 6AB24DC123EB546D00530BA5 /* SceneDelegate.swift */, 94 | 6AB24DCD23EB546F00530BA5 /* Info.plist */, 95 | ); 96 | path = Interview; 97 | sourceTree = ""; 98 | }; 99 | 6AB24DD523EB546F00530BA5 /* InterviewTests */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | B163814B28775C780096BF33 /* Scenes */, 103 | 6AB24DD823EB546F00530BA5 /* Info.plist */, 104 | ); 105 | path = InterviewTests; 106 | sourceTree = ""; 107 | }; 108 | 6AB24DEB23EB54F500530BA5 /* Scenes */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | 6AB24DEC23EB550100530BA5 /* ListContacts */, 112 | ); 113 | path = Scenes; 114 | sourceTree = ""; 115 | }; 116 | 6AB24DEC23EB550100530BA5 /* ListContacts */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | 6AB24DED23EB551000530BA5 /* Models */, 120 | 6AB24DF323EB555B00530BA5 /* Services */, 121 | 6AB24DF023EB552D00530BA5 /* ViewModels */, 122 | 6AB24DF623EB557200530BA5 /* Views */, 123 | ); 124 | path = ListContacts; 125 | sourceTree = ""; 126 | }; 127 | 6AB24DED23EB551000530BA5 /* Models */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | 6AB24DEE23EB552100530BA5 /* Contact.swift */, 131 | ); 132 | path = Models; 133 | sourceTree = ""; 134 | }; 135 | 6AB24DF023EB552D00530BA5 /* ViewModels */ = { 136 | isa = PBXGroup; 137 | children = ( 138 | 6AB24DF123EB554300530BA5 /* ListContactsViewModel.swift */, 139 | ); 140 | path = ViewModels; 141 | sourceTree = ""; 142 | }; 143 | 6AB24DF323EB555B00530BA5 /* Services */ = { 144 | isa = PBXGroup; 145 | children = ( 146 | 6AB24DF423EB556700530BA5 /* ListContactsService.swift */, 147 | ); 148 | path = Services; 149 | sourceTree = ""; 150 | }; 151 | 6AB24DF623EB557200530BA5 /* Views */ = { 152 | isa = PBXGroup; 153 | children = ( 154 | 6AB24DF723EB558D00530BA5 /* ContactCell.swift */, 155 | 6AB24DE923EB54E800530BA5 /* ListContactsViewController.swift */, 156 | ); 157 | path = Views; 158 | sourceTree = ""; 159 | }; 160 | 6AB24DF923EB55D400530BA5 /* Resources */ = { 161 | isa = PBXGroup; 162 | children = ( 163 | 6AB24DC823EB546F00530BA5 /* Assets.xcassets */, 164 | 6AB24DCA23EB546F00530BA5 /* LaunchScreen.storyboard */, 165 | ); 166 | path = Resources; 167 | sourceTree = ""; 168 | }; 169 | B163814B28775C780096BF33 /* Scenes */ = { 170 | isa = PBXGroup; 171 | children = ( 172 | B163814C28775C810096BF33 /* ListContacts */, 173 | ); 174 | path = Scenes; 175 | sourceTree = ""; 176 | }; 177 | B163814C28775C810096BF33 /* ListContacts */ = { 178 | isa = PBXGroup; 179 | children = ( 180 | B163814E28775C8C0096BF33 /* Service */, 181 | B163814D28775C870096BF33 /* ViewModel */, 182 | ); 183 | path = ListContacts; 184 | sourceTree = ""; 185 | }; 186 | B163814D28775C870096BF33 /* ViewModel */ = { 187 | isa = PBXGroup; 188 | children = ( 189 | B163814F28775CAD0096BF33 /* ListContactViewModelTests.swift */, 190 | ); 191 | path = ViewModel; 192 | sourceTree = ""; 193 | }; 194 | B163814E28775C8C0096BF33 /* Service */ = { 195 | isa = PBXGroup; 196 | children = ( 197 | 6ABE2F8F24CEF691009D7593 /* ListContactServiceTests.swift */, 198 | ); 199 | path = Service; 200 | sourceTree = ""; 201 | }; 202 | /* End PBXGroup section */ 203 | 204 | /* Begin PBXNativeTarget section */ 205 | 6AB24DBB23EB546D00530BA5 /* Interview */ = { 206 | isa = PBXNativeTarget; 207 | buildConfigurationList = 6AB24DDB23EB546F00530BA5 /* Build configuration list for PBXNativeTarget "Interview" */; 208 | buildPhases = ( 209 | 6AB24DB823EB546D00530BA5 /* Sources */, 210 | 6AB24DB923EB546D00530BA5 /* Frameworks */, 211 | 6AB24DBA23EB546D00530BA5 /* Resources */, 212 | ); 213 | buildRules = ( 214 | ); 215 | dependencies = ( 216 | ); 217 | name = Interview; 218 | productName = Interview; 219 | productReference = 6AB24DBC23EB546D00530BA5 /* Interview.app */; 220 | productType = "com.apple.product-type.application"; 221 | }; 222 | 6AB24DD123EB546F00530BA5 /* InterviewTests */ = { 223 | isa = PBXNativeTarget; 224 | buildConfigurationList = 6AB24DDE23EB546F00530BA5 /* Build configuration list for PBXNativeTarget "InterviewTests" */; 225 | buildPhases = ( 226 | 6AB24DCE23EB546F00530BA5 /* Sources */, 227 | 6AB24DCF23EB546F00530BA5 /* Frameworks */, 228 | 6AB24DD023EB546F00530BA5 /* Resources */, 229 | ); 230 | buildRules = ( 231 | ); 232 | dependencies = ( 233 | 6AB24DD423EB546F00530BA5 /* PBXTargetDependency */, 234 | ); 235 | name = InterviewTests; 236 | productName = InterviewTests; 237 | productReference = 6AB24DD223EB546F00530BA5 /* InterviewTests.xctest */; 238 | productType = "com.apple.product-type.bundle.unit-test"; 239 | }; 240 | /* End PBXNativeTarget section */ 241 | 242 | /* Begin PBXProject section */ 243 | 6AB24DB423EB546D00530BA5 /* Project object */ = { 244 | isa = PBXProject; 245 | attributes = { 246 | LastSwiftUpdateCheck = 1130; 247 | LastUpgradeCheck = 1130; 248 | ORGANIZATIONNAME = PicPay; 249 | TargetAttributes = { 250 | 6AB24DBB23EB546D00530BA5 = { 251 | CreatedOnToolsVersion = 11.3.1; 252 | }; 253 | 6AB24DD123EB546F00530BA5 = { 254 | CreatedOnToolsVersion = 11.3.1; 255 | TestTargetID = 6AB24DBB23EB546D00530BA5; 256 | }; 257 | }; 258 | }; 259 | buildConfigurationList = 6AB24DB723EB546D00530BA5 /* Build configuration list for PBXProject "Interview" */; 260 | compatibilityVersion = "Xcode 9.3"; 261 | developmentRegion = en; 262 | hasScannedForEncodings = 0; 263 | knownRegions = ( 264 | en, 265 | Base, 266 | ); 267 | mainGroup = 6AB24DB323EB546D00530BA5; 268 | productRefGroup = 6AB24DBD23EB546D00530BA5 /* Products */; 269 | projectDirPath = ""; 270 | projectRoot = ""; 271 | targets = ( 272 | 6AB24DBB23EB546D00530BA5 /* Interview */, 273 | 6AB24DD123EB546F00530BA5 /* InterviewTests */, 274 | ); 275 | }; 276 | /* End PBXProject section */ 277 | 278 | /* Begin PBXResourcesBuildPhase section */ 279 | 6AB24DBA23EB546D00530BA5 /* Resources */ = { 280 | isa = PBXResourcesBuildPhase; 281 | buildActionMask = 2147483647; 282 | files = ( 283 | 6AB24DCC23EB546F00530BA5 /* LaunchScreen.storyboard in Resources */, 284 | 6AB24DC923EB546F00530BA5 /* Assets.xcassets in Resources */, 285 | ); 286 | runOnlyForDeploymentPostprocessing = 0; 287 | }; 288 | 6AB24DD023EB546F00530BA5 /* Resources */ = { 289 | isa = PBXResourcesBuildPhase; 290 | buildActionMask = 2147483647; 291 | files = ( 292 | ); 293 | runOnlyForDeploymentPostprocessing = 0; 294 | }; 295 | /* End PBXResourcesBuildPhase section */ 296 | 297 | /* Begin PBXSourcesBuildPhase section */ 298 | 6AB24DB823EB546D00530BA5 /* Sources */ = { 299 | isa = PBXSourcesBuildPhase; 300 | buildActionMask = 2147483647; 301 | files = ( 302 | 6AB24DEF23EB552100530BA5 /* Contact.swift in Sources */, 303 | 6AB24DEA23EB54E800530BA5 /* ListContactsViewController.swift in Sources */, 304 | 6AB24DF523EB556700530BA5 /* ListContactsService.swift in Sources */, 305 | 6AB24DC023EB546D00530BA5 /* AppDelegate.swift in Sources */, 306 | 6AB24DF223EB554300530BA5 /* ListContactsViewModel.swift in Sources */, 307 | 6AB24DF823EB558D00530BA5 /* ContactCell.swift in Sources */, 308 | 6AB24DC223EB546D00530BA5 /* SceneDelegate.swift in Sources */, 309 | ); 310 | runOnlyForDeploymentPostprocessing = 0; 311 | }; 312 | 6AB24DCE23EB546F00530BA5 /* Sources */ = { 313 | isa = PBXSourcesBuildPhase; 314 | buildActionMask = 2147483647; 315 | files = ( 316 | 6ABE2F9024CEF691009D7593 /* ListContactServiceTests.swift in Sources */, 317 | B163815028775CAD0096BF33 /* ListContactViewModelTests.swift in Sources */, 318 | ); 319 | runOnlyForDeploymentPostprocessing = 0; 320 | }; 321 | /* End PBXSourcesBuildPhase section */ 322 | 323 | /* Begin PBXTargetDependency section */ 324 | 6AB24DD423EB546F00530BA5 /* PBXTargetDependency */ = { 325 | isa = PBXTargetDependency; 326 | target = 6AB24DBB23EB546D00530BA5 /* Interview */; 327 | targetProxy = 6AB24DD323EB546F00530BA5 /* PBXContainerItemProxy */; 328 | }; 329 | /* End PBXTargetDependency section */ 330 | 331 | /* Begin PBXVariantGroup section */ 332 | 6AB24DCA23EB546F00530BA5 /* LaunchScreen.storyboard */ = { 333 | isa = PBXVariantGroup; 334 | children = ( 335 | 6AB24DCB23EB546F00530BA5 /* Base */, 336 | ); 337 | name = LaunchScreen.storyboard; 338 | sourceTree = ""; 339 | }; 340 | /* End PBXVariantGroup section */ 341 | 342 | /* Begin XCBuildConfiguration section */ 343 | 6AB24DD923EB546F00530BA5 /* Debug */ = { 344 | isa = XCBuildConfiguration; 345 | buildSettings = { 346 | ALWAYS_SEARCH_USER_PATHS = NO; 347 | CLANG_ANALYZER_NONNULL = YES; 348 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 349 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 350 | CLANG_CXX_LIBRARY = "libc++"; 351 | CLANG_ENABLE_MODULES = YES; 352 | CLANG_ENABLE_OBJC_ARC = YES; 353 | CLANG_ENABLE_OBJC_WEAK = YES; 354 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 355 | CLANG_WARN_BOOL_CONVERSION = YES; 356 | CLANG_WARN_COMMA = YES; 357 | CLANG_WARN_CONSTANT_CONVERSION = YES; 358 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 359 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 360 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 361 | CLANG_WARN_EMPTY_BODY = YES; 362 | CLANG_WARN_ENUM_CONVERSION = YES; 363 | CLANG_WARN_INFINITE_RECURSION = YES; 364 | CLANG_WARN_INT_CONVERSION = YES; 365 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 366 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 367 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 368 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 369 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 370 | CLANG_WARN_STRICT_PROTOTYPES = YES; 371 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 372 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 373 | CLANG_WARN_UNREACHABLE_CODE = YES; 374 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 375 | COPY_PHASE_STRIP = NO; 376 | DEBUG_INFORMATION_FORMAT = dwarf; 377 | ENABLE_STRICT_OBJC_MSGSEND = YES; 378 | ENABLE_TESTABILITY = YES; 379 | GCC_C_LANGUAGE_STANDARD = gnu11; 380 | GCC_DYNAMIC_NO_PIC = NO; 381 | GCC_NO_COMMON_BLOCKS = YES; 382 | GCC_OPTIMIZATION_LEVEL = 0; 383 | GCC_PREPROCESSOR_DEFINITIONS = ( 384 | "DEBUG=1", 385 | "$(inherited)", 386 | ); 387 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 388 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 389 | GCC_WARN_UNDECLARED_SELECTOR = YES; 390 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 391 | GCC_WARN_UNUSED_FUNCTION = YES; 392 | GCC_WARN_UNUSED_VARIABLE = YES; 393 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 394 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 395 | MTL_FAST_MATH = YES; 396 | ONLY_ACTIVE_ARCH = YES; 397 | SDKROOT = iphoneos; 398 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 399 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 400 | }; 401 | name = Debug; 402 | }; 403 | 6AB24DDA23EB546F00530BA5 /* Release */ = { 404 | isa = XCBuildConfiguration; 405 | buildSettings = { 406 | ALWAYS_SEARCH_USER_PATHS = NO; 407 | CLANG_ANALYZER_NONNULL = YES; 408 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 409 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 410 | CLANG_CXX_LIBRARY = "libc++"; 411 | CLANG_ENABLE_MODULES = YES; 412 | CLANG_ENABLE_OBJC_ARC = YES; 413 | CLANG_ENABLE_OBJC_WEAK = YES; 414 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 415 | CLANG_WARN_BOOL_CONVERSION = YES; 416 | CLANG_WARN_COMMA = YES; 417 | CLANG_WARN_CONSTANT_CONVERSION = YES; 418 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 419 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 420 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 421 | CLANG_WARN_EMPTY_BODY = YES; 422 | CLANG_WARN_ENUM_CONVERSION = YES; 423 | CLANG_WARN_INFINITE_RECURSION = YES; 424 | CLANG_WARN_INT_CONVERSION = YES; 425 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 426 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 427 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 428 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 429 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 430 | CLANG_WARN_STRICT_PROTOTYPES = YES; 431 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 432 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 433 | CLANG_WARN_UNREACHABLE_CODE = YES; 434 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 435 | COPY_PHASE_STRIP = NO; 436 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 437 | ENABLE_NS_ASSERTIONS = NO; 438 | ENABLE_STRICT_OBJC_MSGSEND = YES; 439 | GCC_C_LANGUAGE_STANDARD = gnu11; 440 | GCC_NO_COMMON_BLOCKS = YES; 441 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 442 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 443 | GCC_WARN_UNDECLARED_SELECTOR = YES; 444 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 445 | GCC_WARN_UNUSED_FUNCTION = YES; 446 | GCC_WARN_UNUSED_VARIABLE = YES; 447 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 448 | MTL_ENABLE_DEBUG_INFO = NO; 449 | MTL_FAST_MATH = YES; 450 | SDKROOT = iphoneos; 451 | SWIFT_COMPILATION_MODE = wholemodule; 452 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 453 | VALIDATE_PRODUCT = YES; 454 | }; 455 | name = Release; 456 | }; 457 | 6AB24DDC23EB546F00530BA5 /* Debug */ = { 458 | isa = XCBuildConfiguration; 459 | buildSettings = { 460 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 461 | CODE_SIGN_STYLE = Automatic; 462 | DEVELOPMENT_TEAM = S96TK27X79; 463 | INFOPLIST_FILE = Interview/Info.plist; 464 | LD_RUNPATH_SEARCH_PATHS = ( 465 | "$(inherited)", 466 | "@executable_path/Frameworks", 467 | ); 468 | PRODUCT_BUNDLE_IDENTIFIER = com.picpay.Interview; 469 | PRODUCT_NAME = "$(TARGET_NAME)"; 470 | SWIFT_VERSION = 5.0; 471 | TARGETED_DEVICE_FAMILY = 1; 472 | }; 473 | name = Debug; 474 | }; 475 | 6AB24DDD23EB546F00530BA5 /* Release */ = { 476 | isa = XCBuildConfiguration; 477 | buildSettings = { 478 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 479 | CODE_SIGN_STYLE = Automatic; 480 | DEVELOPMENT_TEAM = S96TK27X79; 481 | INFOPLIST_FILE = Interview/Info.plist; 482 | LD_RUNPATH_SEARCH_PATHS = ( 483 | "$(inherited)", 484 | "@executable_path/Frameworks", 485 | ); 486 | PRODUCT_BUNDLE_IDENTIFIER = com.picpay.Interview; 487 | PRODUCT_NAME = "$(TARGET_NAME)"; 488 | SWIFT_VERSION = 5.0; 489 | TARGETED_DEVICE_FAMILY = 1; 490 | }; 491 | name = Release; 492 | }; 493 | 6AB24DDF23EB546F00530BA5 /* Debug */ = { 494 | isa = XCBuildConfiguration; 495 | buildSettings = { 496 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 497 | BUNDLE_LOADER = "$(TEST_HOST)"; 498 | CODE_SIGN_STYLE = Automatic; 499 | DEVELOPMENT_TEAM = S96TK27X79; 500 | INFOPLIST_FILE = InterviewTests/Info.plist; 501 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 502 | LD_RUNPATH_SEARCH_PATHS = ( 503 | "$(inherited)", 504 | "@executable_path/Frameworks", 505 | "@loader_path/Frameworks", 506 | ); 507 | PRODUCT_BUNDLE_IDENTIFIER = com.picpay.InterviewTests; 508 | PRODUCT_NAME = "$(TARGET_NAME)"; 509 | SWIFT_VERSION = 5.0; 510 | TARGETED_DEVICE_FAMILY = "1,2"; 511 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Interview.app/Interview"; 512 | }; 513 | name = Debug; 514 | }; 515 | 6AB24DE023EB546F00530BA5 /* Release */ = { 516 | isa = XCBuildConfiguration; 517 | buildSettings = { 518 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 519 | BUNDLE_LOADER = "$(TEST_HOST)"; 520 | CODE_SIGN_STYLE = Automatic; 521 | DEVELOPMENT_TEAM = S96TK27X79; 522 | INFOPLIST_FILE = InterviewTests/Info.plist; 523 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 524 | LD_RUNPATH_SEARCH_PATHS = ( 525 | "$(inherited)", 526 | "@executable_path/Frameworks", 527 | "@loader_path/Frameworks", 528 | ); 529 | PRODUCT_BUNDLE_IDENTIFIER = com.picpay.InterviewTests; 530 | PRODUCT_NAME = "$(TARGET_NAME)"; 531 | SWIFT_VERSION = 5.0; 532 | TARGETED_DEVICE_FAMILY = "1,2"; 533 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Interview.app/Interview"; 534 | }; 535 | name = Release; 536 | }; 537 | /* End XCBuildConfiguration section */ 538 | 539 | /* Begin XCConfigurationList section */ 540 | 6AB24DB723EB546D00530BA5 /* Build configuration list for PBXProject "Interview" */ = { 541 | isa = XCConfigurationList; 542 | buildConfigurations = ( 543 | 6AB24DD923EB546F00530BA5 /* Debug */, 544 | 6AB24DDA23EB546F00530BA5 /* Release */, 545 | ); 546 | defaultConfigurationIsVisible = 0; 547 | defaultConfigurationName = Release; 548 | }; 549 | 6AB24DDB23EB546F00530BA5 /* Build configuration list for PBXNativeTarget "Interview" */ = { 550 | isa = XCConfigurationList; 551 | buildConfigurations = ( 552 | 6AB24DDC23EB546F00530BA5 /* Debug */, 553 | 6AB24DDD23EB546F00530BA5 /* Release */, 554 | ); 555 | defaultConfigurationIsVisible = 0; 556 | defaultConfigurationName = Release; 557 | }; 558 | 6AB24DDE23EB546F00530BA5 /* Build configuration list for PBXNativeTarget "InterviewTests" */ = { 559 | isa = XCConfigurationList; 560 | buildConfigurations = ( 561 | 6AB24DDF23EB546F00530BA5 /* Debug */, 562 | 6AB24DE023EB546F00530BA5 /* Release */, 563 | ); 564 | defaultConfigurationIsVisible = 0; 565 | defaultConfigurationName = Release; 566 | }; 567 | /* End XCConfigurationList section */ 568 | }; 569 | rootObject = 6AB24DB423EB546D00530BA5 /* Project object */; 570 | } 571 | -------------------------------------------------------------------------------- /Interview.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Interview.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Interview/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | @UIApplicationMain 4 | class AppDelegate: UIResponder, UIApplicationDelegate { 5 | 6 | 7 | 8 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 9 | // Override point for customization after application launch. 10 | return true 11 | } 12 | 13 | // MARK: UISceneSession Lifecycle 14 | 15 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 16 | // Called when a new scene session is being created. 17 | // Use this method to select a configuration to create the new scene with. 18 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 19 | } 20 | 21 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 22 | // Called when the user discards a scene session. 23 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 24 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 25 | } 26 | 27 | 28 | } 29 | 30 | -------------------------------------------------------------------------------- /Interview/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | 50 | UISupportedInterfaceOrientations~ipad 51 | 52 | UIInterfaceOrientationPortrait 53 | UIInterfaceOrientationPortraitUpsideDown 54 | UIInterfaceOrientationLandscapeLeft 55 | UIInterfaceOrientationLandscapeRight 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Interview/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 | } -------------------------------------------------------------------------------- /Interview/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Interview/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 | -------------------------------------------------------------------------------- /Interview/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 4 | 5 | var window: UIWindow? 6 | 7 | 8 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 9 | if let windowScene = scene as? UIWindowScene { 10 | let window = UIWindow(windowScene: windowScene) 11 | window.rootViewController = UINavigationController(rootViewController: ListContactsViewController()) 12 | self.window = window 13 | window.makeKeyAndVisible() 14 | 15 | } 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /Interview/Scenes/ListContacts/Models/Contact.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /* 4 | Json Contract 5 | [ 6 | { 7 | "id": 1, 8 | "name": "Shakira", 9 | "photoURL": "https://picsum.photos/id/237/200/" 10 | } 11 | ] 12 | */ 13 | 14 | class Contact: Codable { 15 | var id: Int 16 | var name: String = "" 17 | var photoURL = "" 18 | 19 | init(id: Int, name: String, photoURL: String) { 20 | self.id = id 21 | self.name = name 22 | self.photoURL = photoURL 23 | } 24 | 25 | enum CodingKeys: String, CodingKey { 26 | case name = "name" 27 | case photoURL = "photoURL" 28 | case id = "id" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Interview/Scenes/ListContacts/Services/ListContactsService.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | private let apiURL = "https://669ff1b9b132e2c136ffa741.mockapi.io/picpay/ios/interview/contacts" 4 | 5 | class ListContactService { 6 | func fetchContacts(completion: @escaping ([Contact]?, Error?) -> Void) { 7 | guard let api = URL(string: apiURL) else { 8 | return 9 | } 10 | 11 | let session = URLSession.shared 12 | let task = session.dataTask(with: api) { (data, response, error) in 13 | guard let jsonData = data else { 14 | return 15 | } 16 | 17 | do { 18 | let decoder = JSONDecoder() 19 | let decoded = try decoder.decode([Contact].self, from: jsonData) 20 | 21 | completion(decoded, nil) 22 | } catch let error { 23 | completion(nil, error) 24 | } 25 | } 26 | 27 | task.resume() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Interview/Scenes/ListContacts/ViewModels/ListContactsViewModel.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | class ListContactsViewModel { 4 | private let service = ListContactService() 5 | 6 | private var completion: (([Contact]?, Error?) -> Void)? 7 | 8 | init() { } 9 | 10 | func loadContacts(_ completion: @escaping ([Contact]?, Error?) -> Void) { 11 | self.completion = completion 12 | service.fetchContacts { contacts, err in 13 | self.handle(contacts, err) 14 | } 15 | } 16 | 17 | private func handle(_ contacts: [Contact]?, _ error: Error?) { 18 | if let e = error { 19 | completion?(nil, e) 20 | } 21 | 22 | if let contacts = contacts { 23 | completion?(contacts, nil) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Interview/Scenes/ListContacts/Views/ContactCell.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class ContactCell: UITableViewCell { 4 | lazy var contactImage: UIImageView = { 5 | let imgView = UIImageView() 6 | imgView.translatesAutoresizingMaskIntoConstraints = false 7 | imgView.contentMode = .scaleAspectFit 8 | imgView.clipsToBounds = true 9 | return imgView 10 | }() 11 | 12 | lazy var fullnameLabel: UILabel = { 13 | let label = UILabel() 14 | label.font = UIFont.systemFont(ofSize: 20, weight: .semibold) 15 | label.translatesAutoresizingMaskIntoConstraints = false 16 | return label 17 | }() 18 | 19 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 20 | super.init(style: style, reuseIdentifier: reuseIdentifier) 21 | 22 | configureViews() 23 | } 24 | 25 | required init?(coder: NSCoder) { 26 | super.init(coder: coder) 27 | 28 | configureViews() 29 | } 30 | 31 | func configureViews() { 32 | contentView.addSubview(contactImage) 33 | contentView.addSubview(fullnameLabel) 34 | 35 | contactImage.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 15).isActive = true 36 | contactImage.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true 37 | contactImage.heightAnchor.constraint(equalToConstant: 100).isActive = true 38 | contactImage.widthAnchor.constraint(equalToConstant: 100).isActive = true 39 | 40 | fullnameLabel.leadingAnchor.constraint(equalTo: contactImage.trailingAnchor, constant: 16).isActive = true 41 | fullnameLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -15).isActive = true 42 | fullnameLabel.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true 43 | fullnameLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Interview/Scenes/ListContacts/Views/ListContactsViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class UserIdsLegacy { 4 | static let legacyIds = [10, 11, 12, 13] 5 | 6 | static func isLegacy(id: Int) -> Bool { 7 | return legacyIds.contains(id) 8 | } 9 | } 10 | 11 | class ListContactsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { 12 | lazy var activity: UIActivityIndicatorView = { 13 | let activity = UIActivityIndicatorView() 14 | activity.hidesWhenStopped = true 15 | activity.startAnimating() 16 | return activity 17 | }() 18 | 19 | lazy var tableView: UITableView = { 20 | let tableView = UITableView(frame: .zero, style: .plain) 21 | tableView.translatesAutoresizingMaskIntoConstraints = false 22 | tableView.dataSource = self 23 | tableView.delegate = self 24 | tableView.rowHeight = 120 25 | tableView.register(ContactCell.self, forCellReuseIdentifier: String(describing: ContactCell.self)) 26 | tableView.backgroundView = activity 27 | tableView.tableFooterView = UIView() 28 | return tableView 29 | }() 30 | 31 | var contacts = [Contact]() 32 | var viewModel: ListContactsViewModel! 33 | 34 | init() { 35 | super.init(nibName: nil, bundle: nil) 36 | } 37 | 38 | required init?(coder aDecoder: NSCoder) { 39 | fatalError("init(coder:) has not been implemented") 40 | } 41 | 42 | override func loadView() { 43 | let view = UIView() 44 | view.backgroundColor = .white 45 | 46 | self.view = view 47 | } 48 | 49 | override func viewDidLoad() { 50 | super.viewDidLoad() 51 | viewModel = ListContactsViewModel() 52 | configureViews() 53 | 54 | navigationController?.title = "Lista de contatos" 55 | 56 | loadData() 57 | } 58 | 59 | func configureViews() { 60 | view.backgroundColor = .red 61 | view.addSubview(tableView) 62 | NSLayoutConstraint.activate([ 63 | tableView.topAnchor.constraint(equalTo: view.topAnchor), 64 | tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), 65 | tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), 66 | tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor) 67 | ]) 68 | } 69 | 70 | func isLegacy(contact: Contact) -> Bool { 71 | return UserIdsLegacy.isLegacy(id: contact.id) 72 | } 73 | 74 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 75 | return contacts.count 76 | } 77 | 78 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 79 | guard let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: ContactCell.self), for: indexPath) as? ContactCell else { 80 | return UITableViewCell() 81 | } 82 | 83 | let contact = contacts[indexPath.row] 84 | cell.fullnameLabel.text = contact.name 85 | 86 | if let urlPhoto = URL(string: contact.photoURL) { 87 | do { 88 | let data = try Data(contentsOf: urlPhoto) 89 | let image = UIImage(data: data) 90 | cell.contactImage.image = image 91 | } catch _ {} 92 | } 93 | 94 | return cell 95 | } 96 | 97 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 98 | let contato = contacts[indexPath.row - 1] 99 | 100 | guard isLegacy(contact: contato) else { 101 | let alert = UIAlertController(title: "Você tocou em", message: "\(contato.name)", preferredStyle: .alert) 102 | alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) 103 | self.present(alert, animated: true) 104 | return 105 | } 106 | 107 | let alert = UIAlertController(title: "Atenção", message:"Você tocou no contato sorteado", preferredStyle: .alert) 108 | alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) 109 | self.present(alert, animated: true) 110 | } 111 | 112 | func loadData() { 113 | viewModel.loadContacts { contacts, error in 114 | DispatchQueue.main.async { 115 | if let error = error { 116 | print(error) 117 | 118 | let alert = UIAlertController(title: "Ops, ocorreu um erro", message: error.localizedDescription, preferredStyle: .alert) 119 | alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) 120 | self.present(alert, animated: true) 121 | return 122 | } 123 | 124 | self.contacts = contacts ?? [] 125 | self.tableView.reloadData() 126 | self.activity.stopAnimating() 127 | } 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /InterviewTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /InterviewTests/Scenes/ListContacts/Service/ListContactServiceTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Interview 3 | 4 | class ListContactServiceTests: XCTestCase { 5 | 6 | override func setUp() { 7 | // Put setup code here. This method is called before the invocation of each test method in the class. 8 | } 9 | 10 | override func tearDown() { 11 | // Put teardown code here. This method is called after the invocation of each test method in the class. 12 | } 13 | } 14 | 15 | 16 | var mockData: Data? { 17 | """ 18 | [{ 19 | "id": 2, 20 | "name": "Beyonce", 21 | "photoURL": "https://api.adorable.io/avatars/285/a2.png" 22 | }] 23 | """.data(using: .utf8) 24 | } 25 | -------------------------------------------------------------------------------- /InterviewTests/Scenes/ListContacts/ViewModel/ListContactViewModelTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Interview 3 | 4 | class ListContactViewModelTests: XCTestCase { 5 | 6 | override func setUp() { 7 | // Put setup code here. This method is called before the invocation of each test method in the class. 8 | } 9 | 10 | override func tearDown() { 11 | // Put teardown code here. This method is called after the invocation of each test method in the class. 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # interview-ios 2 | 3 | ## Aviso aos candidatos participando do nosso processo: Faremos um Pair Programming na hora da entrevista. Se atente aos seguintes pontos: 4 | 5 | - 🖥 Esteja com sua máquina e seu Xcode funcionando normalmente; 6 | - ✏️ Se possível, clone ou baixe o projeto. Sinta-se à vontade para executar e estudar o projeto de antemão; 7 | - 🙏🏻 Caso realize alguma alteração, favor revertê-la antes da entrevista; 8 | - 😁 Esteja bem hidratado e aproveite, esperamos que você goste de programar conosco! 9 | --------------------------------------------------------------------------------