├── .gitignore ├── MVVM-Example.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcuserdata │ └── ivanmagda.xcuserdatad │ └── xcschemes │ ├── MVVM-Example.xcscheme │ └── xcschememanagement.plist ├── MVVM-Example ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ └── minion.imageset │ │ ├── Contents.json │ │ └── minion.jpg ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── CaseCountable.swift ├── CellReuseIdentifierlabel.swift ├── DetailTextPresentable.swift ├── ImagePresentable.swift ├── Info.plist ├── MinionModeViewModel.swift ├── SettingsViewController.swift ├── SettingsViewControllerTableViewDataSource.swift ├── SwitchPresentable.swift ├── SwitchWithTextTableViewCell.swift └── TextPresentable.swift ├── MVVM-Generic ├── App.swift ├── AppBuilder.swift ├── AppDelegate.swift ├── AppDetailViewController.swift ├── AppDirector.swift ├── AppTableCellViewModel.swift ├── AppTableViewCell.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── DataManager.swift ├── GCDUtils.swift ├── ImageDownloader.swift ├── Info.plist ├── ItemsViewController.swift ├── Utils.swift └── topapps.json ├── MVVM-Greeting ├── AppDelegate.h ├── AppDelegate.m ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── Person.h ├── Person.m ├── Person.swift ├── PersonViewController.h ├── PersonViewController.m ├── PersonViewController.swift ├── PersonViewModel.h ├── PersonViewModel.m ├── PersonViewModel.swift └── main.m ├── README.md └── resources └── images ├── mvvm-example-minion.png ├── mvvm-generic-apps-list.png ├── mvvm-generic-detail.png └── mvvm-greeting.png /.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 | *.xcuserstate 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | # Packages/ 38 | .build/ 39 | 40 | # CocoaPods 41 | # 42 | # We recommend against adding the Pods directory to your .gitignore. However 43 | # you should judge for yourself, the pros and cons are mentioned at: 44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 45 | # 46 | # Pods/ 47 | 48 | # Carthage 49 | # 50 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 51 | # Carthage/Checkouts 52 | 53 | Carthage/Build 54 | 55 | # fastlane 56 | # 57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 58 | # screenshots whenever they are needed. 59 | # For more information about the recommended setup visit: 60 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 61 | 62 | fastlane/report.xml 63 | fastlane/Preview.html 64 | fastlane/screenshots 65 | fastlane/test_output 66 | -------------------------------------------------------------------------------- /MVVM-Example.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 750F021B1D82ACDC0046F808 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 750F021A1D82ACDC0046F808 /* AppDelegate.swift */; }; 11 | 750F021D1D82ACDC0046F808 /* PersonViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 750F021C1D82ACDC0046F808 /* PersonViewController.swift */; }; 12 | 750F02201D82ACDC0046F808 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 750F021E1D82ACDC0046F808 /* Main.storyboard */; }; 13 | 750F02221D82ACDC0046F808 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 750F02211D82ACDC0046F808 /* Assets.xcassets */; }; 14 | 750F02251D82ACDC0046F808 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 750F02231D82ACDC0046F808 /* LaunchScreen.storyboard */; }; 15 | 750F022E1D82AD260046F808 /* Person.swift in Sources */ = {isa = PBXBuildFile; fileRef = 750F022D1D82AD260046F808 /* Person.swift */; }; 16 | 750F02311D82AD620046F808 /* PersonViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 750F02301D82AD620046F808 /* PersonViewModel.swift */; }; 17 | 7525430D1D80577F00C457A1 /* ImagePresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7525430C1D80577F00C457A1 /* ImagePresentable.swift */; }; 18 | 7525430F1D805A5100C457A1 /* SettingsViewControllerTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7525430E1D805A5100C457A1 /* SettingsViewControllerTableViewDataSource.swift */; }; 19 | 752543111D805DB100C457A1 /* CaseCountable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 752543101D805DB100C457A1 /* CaseCountable.swift */; }; 20 | 752543191D805F9D00C457A1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 752543181D805F9D00C457A1 /* AppDelegate.swift */; }; 21 | 7525431E1D805F9D00C457A1 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7525431C1D805F9D00C457A1 /* Main.storyboard */; }; 22 | 752543201D805F9D00C457A1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7525431F1D805F9D00C457A1 /* Assets.xcassets */; }; 23 | 752543231D805F9D00C457A1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 752543211D805F9D00C457A1 /* LaunchScreen.storyboard */; }; 24 | 754B2FDE1D80EF5500E6A009 /* topapps.json in Resources */ = {isa = PBXBuildFile; fileRef = 754B2FDD1D80EF5500E6A009 /* topapps.json */; }; 25 | 754B2FE11D80EF7600E6A009 /* DataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 754B2FE01D80EF7600E6A009 /* DataManager.swift */; }; 26 | 754B2FE41D80F23700E6A009 /* GCDUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 754B2FE31D80F23700E6A009 /* GCDUtils.swift */; }; 27 | 754B2FE71D80FA5800E6A009 /* AppBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 754B2FE61D80FA5800E6A009 /* AppBuilder.swift */; }; 28 | 754B2FE91D80FAC500E6A009 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 754B2FE81D80FAC500E6A009 /* App.swift */; }; 29 | 754B2FEB1D80FF5B00E6A009 /* ItemsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 754B2FEA1D80FF5B00E6A009 /* ItemsViewController.swift */; }; 30 | 754B2FED1D8104B100E6A009 /* AppDirector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 754B2FEC1D8104B100E6A009 /* AppDirector.swift */; }; 31 | 754B2FEF1D81065400E6A009 /* AppDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 754B2FEE1D81065400E6A009 /* AppDetailViewController.swift */; }; 32 | 754B2FF11D810CC100E6A009 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 754B2FF01D810CC100E6A009 /* Utils.swift */; }; 33 | 75B8CBE31D814BF400EC3062 /* TextPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75DAAD8C1D80192D00BAC0FE /* TextPresentable.swift */; }; 34 | 75B8CBE41D814BF800EC3062 /* SwitchPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75DAAD901D801BB100BAC0FE /* SwitchPresentable.swift */; }; 35 | 75B8CBE51D814BFB00EC3062 /* ImagePresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7525430C1D80577F00C457A1 /* ImagePresentable.swift */; }; 36 | 75B8CBE71D814ECB00EC3062 /* AppTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75B8CBE61D814ECB00EC3062 /* AppTableViewCell.swift */; }; 37 | 75B8CBEA1D81552100EC3062 /* DetailTextPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75B8CBE91D81552100EC3062 /* DetailTextPresentable.swift */; }; 38 | 75B8CBEB1D81558300EC3062 /* DetailTextPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75B8CBE91D81552100EC3062 /* DetailTextPresentable.swift */; }; 39 | 75B8CBED1D81578500EC3062 /* ImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75B8CBEC1D81578500EC3062 /* ImageDownloader.swift */; }; 40 | 75B8CBEF1D8159BC00EC3062 /* AppTableCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75B8CBEE1D8159BC00EC3062 /* AppTableCellViewModel.swift */; }; 41 | 75DAAD6F1D800CDB00BAC0FE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75DAAD6E1D800CDB00BAC0FE /* AppDelegate.swift */; }; 42 | 75DAAD711D800CDB00BAC0FE /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75DAAD701D800CDB00BAC0FE /* SettingsViewController.swift */; }; 43 | 75DAAD741D800CDB00BAC0FE /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 75DAAD721D800CDB00BAC0FE /* Main.storyboard */; }; 44 | 75DAAD761D800CDB00BAC0FE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 75DAAD751D800CDB00BAC0FE /* Assets.xcassets */; }; 45 | 75DAAD791D800CDB00BAC0FE /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 75DAAD771D800CDB00BAC0FE /* LaunchScreen.storyboard */; }; 46 | 75DAAD841D80124100BAC0FE /* SwitchWithTextTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75DAAD831D80124100BAC0FE /* SwitchWithTextTableViewCell.swift */; }; 47 | 75DAAD871D80172D00BAC0FE /* CellReuseIdentifierlabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75DAAD861D80172D00BAC0FE /* CellReuseIdentifierlabel.swift */; }; 48 | 75DAAD8A1D8017AA00BAC0FE /* MinionModeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75DAAD891D8017AA00BAC0FE /* MinionModeViewModel.swift */; }; 49 | 75DAAD8D1D80192D00BAC0FE /* TextPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75DAAD8C1D80192D00BAC0FE /* TextPresentable.swift */; }; 50 | 75DAAD911D801BB100BAC0FE /* SwitchPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75DAAD901D801BB100BAC0FE /* SwitchPresentable.swift */; }; 51 | /* End PBXBuildFile section */ 52 | 53 | /* Begin PBXFileReference section */ 54 | 750F02181D82ACDC0046F808 /* MVVM-Greeting.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "MVVM-Greeting.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 55 | 750F021A1D82ACDC0046F808 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 56 | 750F021C1D82ACDC0046F808 /* PersonViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersonViewController.swift; sourceTree = ""; }; 57 | 750F021F1D82ACDC0046F808 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 58 | 750F02211D82ACDC0046F808 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 59 | 750F02241D82ACDC0046F808 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 60 | 750F02261D82ACDC0046F808 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 61 | 750F022D1D82AD260046F808 /* Person.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Person.swift; sourceTree = ""; }; 62 | 750F02301D82AD620046F808 /* PersonViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersonViewModel.swift; sourceTree = ""; }; 63 | 7525430C1D80577F00C457A1 /* ImagePresentable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImagePresentable.swift; sourceTree = ""; }; 64 | 7525430E1D805A5100C457A1 /* SettingsViewControllerTableViewDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewControllerTableViewDataSource.swift; sourceTree = ""; }; 65 | 752543101D805DB100C457A1 /* CaseCountable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CaseCountable.swift; sourceTree = ""; }; 66 | 752543161D805F9D00C457A1 /* MVVM-Generic.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "MVVM-Generic.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 67 | 752543181D805F9D00C457A1 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 68 | 7525431D1D805F9D00C457A1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 69 | 7525431F1D805F9D00C457A1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 70 | 752543221D805F9D00C457A1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 71 | 752543241D805F9D00C457A1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 72 | 754B2FDD1D80EF5500E6A009 /* topapps.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = topapps.json; sourceTree = ""; }; 73 | 754B2FE01D80EF7600E6A009 /* DataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataManager.swift; sourceTree = ""; }; 74 | 754B2FE31D80F23700E6A009 /* GCDUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GCDUtils.swift; sourceTree = ""; }; 75 | 754B2FE61D80FA5800E6A009 /* AppBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppBuilder.swift; sourceTree = ""; }; 76 | 754B2FE81D80FAC500E6A009 /* App.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; 77 | 754B2FEA1D80FF5B00E6A009 /* ItemsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemsViewController.swift; sourceTree = ""; }; 78 | 754B2FEC1D8104B100E6A009 /* AppDirector.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDirector.swift; sourceTree = ""; }; 79 | 754B2FEE1D81065400E6A009 /* AppDetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDetailViewController.swift; sourceTree = ""; }; 80 | 754B2FF01D810CC100E6A009 /* Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = ""; }; 81 | 75B8CBE61D814ECB00EC3062 /* AppTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppTableViewCell.swift; sourceTree = ""; }; 82 | 75B8CBE91D81552100EC3062 /* DetailTextPresentable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailTextPresentable.swift; sourceTree = ""; }; 83 | 75B8CBEC1D81578500EC3062 /* ImageDownloader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageDownloader.swift; sourceTree = ""; }; 84 | 75B8CBEE1D8159BC00EC3062 /* AppTableCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppTableCellViewModel.swift; sourceTree = ""; }; 85 | 75DAAD6B1D800CDB00BAC0FE /* MVVM-Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "MVVM-Example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 86 | 75DAAD6E1D800CDB00BAC0FE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 87 | 75DAAD701D800CDB00BAC0FE /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; 88 | 75DAAD731D800CDB00BAC0FE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 89 | 75DAAD751D800CDB00BAC0FE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 90 | 75DAAD781D800CDB00BAC0FE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 91 | 75DAAD7A1D800CDB00BAC0FE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 92 | 75DAAD831D80124100BAC0FE /* SwitchWithTextTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwitchWithTextTableViewCell.swift; sourceTree = ""; }; 93 | 75DAAD861D80172D00BAC0FE /* CellReuseIdentifierlabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CellReuseIdentifierlabel.swift; sourceTree = ""; }; 94 | 75DAAD891D8017AA00BAC0FE /* MinionModeViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MinionModeViewModel.swift; sourceTree = ""; }; 95 | 75DAAD8C1D80192D00BAC0FE /* TextPresentable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextPresentable.swift; sourceTree = ""; }; 96 | 75DAAD901D801BB100BAC0FE /* SwitchPresentable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwitchPresentable.swift; sourceTree = ""; }; 97 | /* End PBXFileReference section */ 98 | 99 | /* Begin PBXFrameworksBuildPhase section */ 100 | 750F02151D82ACDC0046F808 /* Frameworks */ = { 101 | isa = PBXFrameworksBuildPhase; 102 | buildActionMask = 2147483647; 103 | files = ( 104 | ); 105 | runOnlyForDeploymentPostprocessing = 0; 106 | }; 107 | 752543131D805F9D00C457A1 /* Frameworks */ = { 108 | isa = PBXFrameworksBuildPhase; 109 | buildActionMask = 2147483647; 110 | files = ( 111 | ); 112 | runOnlyForDeploymentPostprocessing = 0; 113 | }; 114 | 75DAAD681D800CDB00BAC0FE /* Frameworks */ = { 115 | isa = PBXFrameworksBuildPhase; 116 | buildActionMask = 2147483647; 117 | files = ( 118 | ); 119 | runOnlyForDeploymentPostprocessing = 0; 120 | }; 121 | /* End PBXFrameworksBuildPhase section */ 122 | 123 | /* Begin PBXGroup section */ 124 | 750F02191D82ACDC0046F808 /* MVVM-Greeting */ = { 125 | isa = PBXGroup; 126 | children = ( 127 | 750F021A1D82ACDC0046F808 /* AppDelegate.swift */, 128 | 750F022F1D82AD540046F808 /* Model */, 129 | 750F02321D82AD730046F808 /* ViewModel */, 130 | 750F022C1D82ACF70046F808 /* Controllers */, 131 | 750F022A1D82ACE50046F808 /* Assets */, 132 | 750F022B1D82ACEA0046F808 /* Supporting Files */, 133 | ); 134 | path = "MVVM-Greeting"; 135 | sourceTree = ""; 136 | }; 137 | 750F022A1D82ACE50046F808 /* Assets */ = { 138 | isa = PBXGroup; 139 | children = ( 140 | 750F021E1D82ACDC0046F808 /* Main.storyboard */, 141 | 750F02211D82ACDC0046F808 /* Assets.xcassets */, 142 | 750F02231D82ACDC0046F808 /* LaunchScreen.storyboard */, 143 | ); 144 | name = Assets; 145 | sourceTree = ""; 146 | }; 147 | 750F022B1D82ACEA0046F808 /* Supporting Files */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | 750F02261D82ACDC0046F808 /* Info.plist */, 151 | ); 152 | name = "Supporting Files"; 153 | sourceTree = ""; 154 | }; 155 | 750F022C1D82ACF70046F808 /* Controllers */ = { 156 | isa = PBXGroup; 157 | children = ( 158 | 750F021C1D82ACDC0046F808 /* PersonViewController.swift */, 159 | ); 160 | name = Controllers; 161 | sourceTree = ""; 162 | }; 163 | 750F022F1D82AD540046F808 /* Model */ = { 164 | isa = PBXGroup; 165 | children = ( 166 | 750F022D1D82AD260046F808 /* Person.swift */, 167 | ); 168 | name = Model; 169 | sourceTree = ""; 170 | }; 171 | 750F02321D82AD730046F808 /* ViewModel */ = { 172 | isa = PBXGroup; 173 | children = ( 174 | 750F02301D82AD620046F808 /* PersonViewModel.swift */, 175 | ); 176 | name = ViewModel; 177 | sourceTree = ""; 178 | }; 179 | 752543171D805F9D00C457A1 /* MVVM-Generic */ = { 180 | isa = PBXGroup; 181 | children = ( 182 | 752543181D805F9D00C457A1 /* AppDelegate.swift */, 183 | 754B2FEC1D8104B100E6A009 /* AppDirector.swift */, 184 | 754B2FE21D80EF8000E6A009 /* Model */, 185 | 752543281D80600200C457A1 /* Controllers */, 186 | 752543291D80601000C457A1 /* Assets */, 187 | 75B8CBE81D814ED000EC3062 /* View */, 188 | 754B2FE51D80F23B00E6A009 /* Utils */, 189 | 754B2FDF1D80EF5A00E6A009 /* Resources */, 190 | 7525432A1D80601900C457A1 /* Supporting Files */, 191 | ); 192 | path = "MVVM-Generic"; 193 | sourceTree = ""; 194 | }; 195 | 752543281D80600200C457A1 /* Controllers */ = { 196 | isa = PBXGroup; 197 | children = ( 198 | 754B2FEA1D80FF5B00E6A009 /* ItemsViewController.swift */, 199 | 754B2FEE1D81065400E6A009 /* AppDetailViewController.swift */, 200 | ); 201 | name = Controllers; 202 | sourceTree = ""; 203 | }; 204 | 752543291D80601000C457A1 /* Assets */ = { 205 | isa = PBXGroup; 206 | children = ( 207 | 7525431C1D805F9D00C457A1 /* Main.storyboard */, 208 | 7525431F1D805F9D00C457A1 /* Assets.xcassets */, 209 | 752543211D805F9D00C457A1 /* LaunchScreen.storyboard */, 210 | ); 211 | name = Assets; 212 | sourceTree = ""; 213 | }; 214 | 7525432A1D80601900C457A1 /* Supporting Files */ = { 215 | isa = PBXGroup; 216 | children = ( 217 | 752543241D805F9D00C457A1 /* Info.plist */, 218 | ); 219 | name = "Supporting Files"; 220 | sourceTree = ""; 221 | }; 222 | 754B2FDF1D80EF5A00E6A009 /* Resources */ = { 223 | isa = PBXGroup; 224 | children = ( 225 | 754B2FDD1D80EF5500E6A009 /* topapps.json */, 226 | ); 227 | name = Resources; 228 | sourceTree = ""; 229 | }; 230 | 754B2FE21D80EF8000E6A009 /* Model */ = { 231 | isa = PBXGroup; 232 | children = ( 233 | 754B2FE81D80FAC500E6A009 /* App.swift */, 234 | 754B2FE01D80EF7600E6A009 /* DataManager.swift */, 235 | 754B2FE61D80FA5800E6A009 /* AppBuilder.swift */, 236 | 75B8CBEC1D81578500EC3062 /* ImageDownloader.swift */, 237 | 75B8CBEE1D8159BC00EC3062 /* AppTableCellViewModel.swift */, 238 | ); 239 | name = Model; 240 | sourceTree = ""; 241 | }; 242 | 754B2FE51D80F23B00E6A009 /* Utils */ = { 243 | isa = PBXGroup; 244 | children = ( 245 | 754B2FE31D80F23700E6A009 /* GCDUtils.swift */, 246 | 754B2FF01D810CC100E6A009 /* Utils.swift */, 247 | ); 248 | name = Utils; 249 | sourceTree = ""; 250 | }; 251 | 75B8CBE81D814ED000EC3062 /* View */ = { 252 | isa = PBXGroup; 253 | children = ( 254 | 75B8CBE61D814ECB00EC3062 /* AppTableViewCell.swift */, 255 | ); 256 | name = View; 257 | sourceTree = ""; 258 | }; 259 | 75DAAD621D800CDB00BAC0FE = { 260 | isa = PBXGroup; 261 | children = ( 262 | 75DAAD6D1D800CDB00BAC0FE /* MVVM-Example */, 263 | 752543171D805F9D00C457A1 /* MVVM-Generic */, 264 | 750F02191D82ACDC0046F808 /* MVVM-Greeting */, 265 | 75DAAD6C1D800CDB00BAC0FE /* Products */, 266 | ); 267 | sourceTree = ""; 268 | }; 269 | 75DAAD6C1D800CDB00BAC0FE /* Products */ = { 270 | isa = PBXGroup; 271 | children = ( 272 | 75DAAD6B1D800CDB00BAC0FE /* MVVM-Example.app */, 273 | 752543161D805F9D00C457A1 /* MVVM-Generic.app */, 274 | 750F02181D82ACDC0046F808 /* MVVM-Greeting.app */, 275 | ); 276 | name = Products; 277 | sourceTree = ""; 278 | }; 279 | 75DAAD6D1D800CDB00BAC0FE /* MVVM-Example */ = { 280 | isa = PBXGroup; 281 | children = ( 282 | 75DAAD6E1D800CDB00BAC0FE /* AppDelegate.swift */, 283 | 75DAAD821D800DBC00BAC0FE /* Controllers */, 284 | 75DAAD8B1D8017B100BAC0FE /* Model */, 285 | 75DAAD881D80174800BAC0FE /* Protocols */, 286 | 75DAAD851D80124800BAC0FE /* View */, 287 | 75DAAD801D800D7A00BAC0FE /* Assets */, 288 | 75DAAD811D800D8000BAC0FE /* Supporting Files */, 289 | ); 290 | path = "MVVM-Example"; 291 | sourceTree = ""; 292 | }; 293 | 75DAAD801D800D7A00BAC0FE /* Assets */ = { 294 | isa = PBXGroup; 295 | children = ( 296 | 75DAAD721D800CDB00BAC0FE /* Main.storyboard */, 297 | 75DAAD751D800CDB00BAC0FE /* Assets.xcassets */, 298 | 75DAAD771D800CDB00BAC0FE /* LaunchScreen.storyboard */, 299 | ); 300 | name = Assets; 301 | sourceTree = ""; 302 | }; 303 | 75DAAD811D800D8000BAC0FE /* Supporting Files */ = { 304 | isa = PBXGroup; 305 | children = ( 306 | 75DAAD7A1D800CDB00BAC0FE /* Info.plist */, 307 | ); 308 | name = "Supporting Files"; 309 | sourceTree = ""; 310 | }; 311 | 75DAAD821D800DBC00BAC0FE /* Controllers */ = { 312 | isa = PBXGroup; 313 | children = ( 314 | 75DAAD701D800CDB00BAC0FE /* SettingsViewController.swift */, 315 | 7525430E1D805A5100C457A1 /* SettingsViewControllerTableViewDataSource.swift */, 316 | ); 317 | name = Controllers; 318 | sourceTree = ""; 319 | }; 320 | 75DAAD851D80124800BAC0FE /* View */ = { 321 | isa = PBXGroup; 322 | children = ( 323 | 75DAAD831D80124100BAC0FE /* SwitchWithTextTableViewCell.swift */, 324 | ); 325 | name = View; 326 | sourceTree = ""; 327 | }; 328 | 75DAAD881D80174800BAC0FE /* Protocols */ = { 329 | isa = PBXGroup; 330 | children = ( 331 | 75DAAD8C1D80192D00BAC0FE /* TextPresentable.swift */, 332 | 75B8CBE91D81552100EC3062 /* DetailTextPresentable.swift */, 333 | 75DAAD901D801BB100BAC0FE /* SwitchPresentable.swift */, 334 | 7525430C1D80577F00C457A1 /* ImagePresentable.swift */, 335 | 75DAAD861D80172D00BAC0FE /* CellReuseIdentifierlabel.swift */, 336 | 752543101D805DB100C457A1 /* CaseCountable.swift */, 337 | ); 338 | name = Protocols; 339 | sourceTree = ""; 340 | }; 341 | 75DAAD8B1D8017B100BAC0FE /* Model */ = { 342 | isa = PBXGroup; 343 | children = ( 344 | 75DAAD891D8017AA00BAC0FE /* MinionModeViewModel.swift */, 345 | ); 346 | name = Model; 347 | sourceTree = ""; 348 | }; 349 | /* End PBXGroup section */ 350 | 351 | /* Begin PBXNativeTarget section */ 352 | 750F02171D82ACDC0046F808 /* MVVM-Greeting */ = { 353 | isa = PBXNativeTarget; 354 | buildConfigurationList = 750F02291D82ACDC0046F808 /* Build configuration list for PBXNativeTarget "MVVM-Greeting" */; 355 | buildPhases = ( 356 | 750F02141D82ACDC0046F808 /* Sources */, 357 | 750F02151D82ACDC0046F808 /* Frameworks */, 358 | 750F02161D82ACDC0046F808 /* Resources */, 359 | ); 360 | buildRules = ( 361 | ); 362 | dependencies = ( 363 | ); 364 | name = "MVVM-Greeting"; 365 | productName = "MVVM-Greeting"; 366 | productReference = 750F02181D82ACDC0046F808 /* MVVM-Greeting.app */; 367 | productType = "com.apple.product-type.application"; 368 | }; 369 | 752543151D805F9D00C457A1 /* MVVM-Generic */ = { 370 | isa = PBXNativeTarget; 371 | buildConfigurationList = 752543251D805F9D00C457A1 /* Build configuration list for PBXNativeTarget "MVVM-Generic" */; 372 | buildPhases = ( 373 | 752543121D805F9D00C457A1 /* Sources */, 374 | 752543131D805F9D00C457A1 /* Frameworks */, 375 | 752543141D805F9D00C457A1 /* Resources */, 376 | ); 377 | buildRules = ( 378 | ); 379 | dependencies = ( 380 | ); 381 | name = "MVVM-Generic"; 382 | productName = "MVVM-Generic"; 383 | productReference = 752543161D805F9D00C457A1 /* MVVM-Generic.app */; 384 | productType = "com.apple.product-type.application"; 385 | }; 386 | 75DAAD6A1D800CDB00BAC0FE /* MVVM-Example */ = { 387 | isa = PBXNativeTarget; 388 | buildConfigurationList = 75DAAD7D1D800CDB00BAC0FE /* Build configuration list for PBXNativeTarget "MVVM-Example" */; 389 | buildPhases = ( 390 | 75DAAD671D800CDB00BAC0FE /* Sources */, 391 | 75DAAD681D800CDB00BAC0FE /* Frameworks */, 392 | 75DAAD691D800CDB00BAC0FE /* Resources */, 393 | ); 394 | buildRules = ( 395 | ); 396 | dependencies = ( 397 | ); 398 | name = "MVVM-Example"; 399 | productName = "MVVM-Example"; 400 | productReference = 75DAAD6B1D800CDB00BAC0FE /* MVVM-Example.app */; 401 | productType = "com.apple.product-type.application"; 402 | }; 403 | /* End PBXNativeTarget section */ 404 | 405 | /* Begin PBXProject section */ 406 | 75DAAD631D800CDB00BAC0FE /* Project object */ = { 407 | isa = PBXProject; 408 | attributes = { 409 | LastSwiftUpdateCheck = 0800; 410 | LastUpgradeCheck = 0900; 411 | ORGANIZATIONNAME = "Ivan Magda"; 412 | TargetAttributes = { 413 | 750F02171D82ACDC0046F808 = { 414 | CreatedOnToolsVersion = 8.0; 415 | DevelopmentTeam = 6UNHY8UY6E; 416 | LastSwiftMigration = 0900; 417 | ProvisioningStyle = Automatic; 418 | }; 419 | 752543151D805F9D00C457A1 = { 420 | CreatedOnToolsVersion = 8.0; 421 | DevelopmentTeam = 6UNHY8UY6E; 422 | LastSwiftMigration = 0900; 423 | ProvisioningStyle = Automatic; 424 | }; 425 | 75DAAD6A1D800CDB00BAC0FE = { 426 | CreatedOnToolsVersion = 8.0; 427 | DevelopmentTeam = 6UNHY8UY6E; 428 | LastSwiftMigration = 0900; 429 | ProvisioningStyle = Automatic; 430 | }; 431 | }; 432 | }; 433 | buildConfigurationList = 75DAAD661D800CDB00BAC0FE /* Build configuration list for PBXProject "MVVM-Example" */; 434 | compatibilityVersion = "Xcode 3.2"; 435 | developmentRegion = English; 436 | hasScannedForEncodings = 0; 437 | knownRegions = ( 438 | en, 439 | Base, 440 | ); 441 | mainGroup = 75DAAD621D800CDB00BAC0FE; 442 | productRefGroup = 75DAAD6C1D800CDB00BAC0FE /* Products */; 443 | projectDirPath = ""; 444 | projectRoot = ""; 445 | targets = ( 446 | 75DAAD6A1D800CDB00BAC0FE /* MVVM-Example */, 447 | 752543151D805F9D00C457A1 /* MVVM-Generic */, 448 | 750F02171D82ACDC0046F808 /* MVVM-Greeting */, 449 | ); 450 | }; 451 | /* End PBXProject section */ 452 | 453 | /* Begin PBXResourcesBuildPhase section */ 454 | 750F02161D82ACDC0046F808 /* Resources */ = { 455 | isa = PBXResourcesBuildPhase; 456 | buildActionMask = 2147483647; 457 | files = ( 458 | 750F02251D82ACDC0046F808 /* LaunchScreen.storyboard in Resources */, 459 | 750F02221D82ACDC0046F808 /* Assets.xcassets in Resources */, 460 | 750F02201D82ACDC0046F808 /* Main.storyboard in Resources */, 461 | ); 462 | runOnlyForDeploymentPostprocessing = 0; 463 | }; 464 | 752543141D805F9D00C457A1 /* Resources */ = { 465 | isa = PBXResourcesBuildPhase; 466 | buildActionMask = 2147483647; 467 | files = ( 468 | 752543231D805F9D00C457A1 /* LaunchScreen.storyboard in Resources */, 469 | 752543201D805F9D00C457A1 /* Assets.xcassets in Resources */, 470 | 7525431E1D805F9D00C457A1 /* Main.storyboard in Resources */, 471 | 754B2FDE1D80EF5500E6A009 /* topapps.json in Resources */, 472 | ); 473 | runOnlyForDeploymentPostprocessing = 0; 474 | }; 475 | 75DAAD691D800CDB00BAC0FE /* Resources */ = { 476 | isa = PBXResourcesBuildPhase; 477 | buildActionMask = 2147483647; 478 | files = ( 479 | 75DAAD791D800CDB00BAC0FE /* LaunchScreen.storyboard in Resources */, 480 | 75DAAD761D800CDB00BAC0FE /* Assets.xcassets in Resources */, 481 | 75DAAD741D800CDB00BAC0FE /* Main.storyboard in Resources */, 482 | ); 483 | runOnlyForDeploymentPostprocessing = 0; 484 | }; 485 | /* End PBXResourcesBuildPhase section */ 486 | 487 | /* Begin PBXSourcesBuildPhase section */ 488 | 750F02141D82ACDC0046F808 /* Sources */ = { 489 | isa = PBXSourcesBuildPhase; 490 | buildActionMask = 2147483647; 491 | files = ( 492 | 750F022E1D82AD260046F808 /* Person.swift in Sources */, 493 | 750F021D1D82ACDC0046F808 /* PersonViewController.swift in Sources */, 494 | 750F021B1D82ACDC0046F808 /* AppDelegate.swift in Sources */, 495 | 750F02311D82AD620046F808 /* PersonViewModel.swift in Sources */, 496 | ); 497 | runOnlyForDeploymentPostprocessing = 0; 498 | }; 499 | 752543121D805F9D00C457A1 /* Sources */ = { 500 | isa = PBXSourcesBuildPhase; 501 | buildActionMask = 2147483647; 502 | files = ( 503 | 754B2FEF1D81065400E6A009 /* AppDetailViewController.swift in Sources */, 504 | 75B8CBED1D81578500EC3062 /* ImageDownloader.swift in Sources */, 505 | 754B2FE41D80F23700E6A009 /* GCDUtils.swift in Sources */, 506 | 754B2FF11D810CC100E6A009 /* Utils.swift in Sources */, 507 | 754B2FED1D8104B100E6A009 /* AppDirector.swift in Sources */, 508 | 754B2FE71D80FA5800E6A009 /* AppBuilder.swift in Sources */, 509 | 75B8CBEB1D81558300EC3062 /* DetailTextPresentable.swift in Sources */, 510 | 754B2FE91D80FAC500E6A009 /* App.swift in Sources */, 511 | 75B8CBE31D814BF400EC3062 /* TextPresentable.swift in Sources */, 512 | 75B8CBE71D814ECB00EC3062 /* AppTableViewCell.swift in Sources */, 513 | 754B2FEB1D80FF5B00E6A009 /* ItemsViewController.swift in Sources */, 514 | 75B8CBEF1D8159BC00EC3062 /* AppTableCellViewModel.swift in Sources */, 515 | 75B8CBE41D814BF800EC3062 /* SwitchPresentable.swift in Sources */, 516 | 75B8CBE51D814BFB00EC3062 /* ImagePresentable.swift in Sources */, 517 | 754B2FE11D80EF7600E6A009 /* DataManager.swift in Sources */, 518 | 752543191D805F9D00C457A1 /* AppDelegate.swift in Sources */, 519 | ); 520 | runOnlyForDeploymentPostprocessing = 0; 521 | }; 522 | 75DAAD671D800CDB00BAC0FE /* Sources */ = { 523 | isa = PBXSourcesBuildPhase; 524 | buildActionMask = 2147483647; 525 | files = ( 526 | 7525430F1D805A5100C457A1 /* SettingsViewControllerTableViewDataSource.swift in Sources */, 527 | 75B8CBEA1D81552100EC3062 /* DetailTextPresentable.swift in Sources */, 528 | 75DAAD871D80172D00BAC0FE /* CellReuseIdentifierlabel.swift in Sources */, 529 | 7525430D1D80577F00C457A1 /* ImagePresentable.swift in Sources */, 530 | 75DAAD911D801BB100BAC0FE /* SwitchPresentable.swift in Sources */, 531 | 75DAAD711D800CDB00BAC0FE /* SettingsViewController.swift in Sources */, 532 | 752543111D805DB100C457A1 /* CaseCountable.swift in Sources */, 533 | 75DAAD841D80124100BAC0FE /* SwitchWithTextTableViewCell.swift in Sources */, 534 | 75DAAD8D1D80192D00BAC0FE /* TextPresentable.swift in Sources */, 535 | 75DAAD8A1D8017AA00BAC0FE /* MinionModeViewModel.swift in Sources */, 536 | 75DAAD6F1D800CDB00BAC0FE /* AppDelegate.swift in Sources */, 537 | ); 538 | runOnlyForDeploymentPostprocessing = 0; 539 | }; 540 | /* End PBXSourcesBuildPhase section */ 541 | 542 | /* Begin PBXVariantGroup section */ 543 | 750F021E1D82ACDC0046F808 /* Main.storyboard */ = { 544 | isa = PBXVariantGroup; 545 | children = ( 546 | 750F021F1D82ACDC0046F808 /* Base */, 547 | ); 548 | name = Main.storyboard; 549 | sourceTree = ""; 550 | }; 551 | 750F02231D82ACDC0046F808 /* LaunchScreen.storyboard */ = { 552 | isa = PBXVariantGroup; 553 | children = ( 554 | 750F02241D82ACDC0046F808 /* Base */, 555 | ); 556 | name = LaunchScreen.storyboard; 557 | sourceTree = ""; 558 | }; 559 | 7525431C1D805F9D00C457A1 /* Main.storyboard */ = { 560 | isa = PBXVariantGroup; 561 | children = ( 562 | 7525431D1D805F9D00C457A1 /* Base */, 563 | ); 564 | name = Main.storyboard; 565 | sourceTree = ""; 566 | }; 567 | 752543211D805F9D00C457A1 /* LaunchScreen.storyboard */ = { 568 | isa = PBXVariantGroup; 569 | children = ( 570 | 752543221D805F9D00C457A1 /* Base */, 571 | ); 572 | name = LaunchScreen.storyboard; 573 | sourceTree = ""; 574 | }; 575 | 75DAAD721D800CDB00BAC0FE /* Main.storyboard */ = { 576 | isa = PBXVariantGroup; 577 | children = ( 578 | 75DAAD731D800CDB00BAC0FE /* Base */, 579 | ); 580 | name = Main.storyboard; 581 | sourceTree = ""; 582 | }; 583 | 75DAAD771D800CDB00BAC0FE /* LaunchScreen.storyboard */ = { 584 | isa = PBXVariantGroup; 585 | children = ( 586 | 75DAAD781D800CDB00BAC0FE /* Base */, 587 | ); 588 | name = LaunchScreen.storyboard; 589 | sourceTree = ""; 590 | }; 591 | /* End PBXVariantGroup section */ 592 | 593 | /* Begin XCBuildConfiguration section */ 594 | 750F02271D82ACDC0046F808 /* Debug */ = { 595 | isa = XCBuildConfiguration; 596 | buildSettings = { 597 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 598 | DEVELOPMENT_TEAM = 6UNHY8UY6E; 599 | INFOPLIST_FILE = "MVVM-Greeting/Info.plist"; 600 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 601 | PRODUCT_BUNDLE_IDENTIFIER = "com.ivanmagda.MVVM-Greeting"; 602 | PRODUCT_NAME = "$(TARGET_NAME)"; 603 | SWIFT_SWIFT3_OBJC_INFERENCE = Off; 604 | SWIFT_VERSION = 4.0; 605 | }; 606 | name = Debug; 607 | }; 608 | 750F02281D82ACDC0046F808 /* Release */ = { 609 | isa = XCBuildConfiguration; 610 | buildSettings = { 611 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 612 | DEVELOPMENT_TEAM = 6UNHY8UY6E; 613 | INFOPLIST_FILE = "MVVM-Greeting/Info.plist"; 614 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 615 | PRODUCT_BUNDLE_IDENTIFIER = "com.ivanmagda.MVVM-Greeting"; 616 | PRODUCT_NAME = "$(TARGET_NAME)"; 617 | SWIFT_SWIFT3_OBJC_INFERENCE = Off; 618 | SWIFT_VERSION = 4.0; 619 | }; 620 | name = Release; 621 | }; 622 | 752543261D805F9D00C457A1 /* Debug */ = { 623 | isa = XCBuildConfiguration; 624 | buildSettings = { 625 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 626 | DEVELOPMENT_TEAM = 6UNHY8UY6E; 627 | INFOPLIST_FILE = "MVVM-Generic/Info.plist"; 628 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 629 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 630 | PRODUCT_BUNDLE_IDENTIFIER = "com.ivanmagda.MVVM-Generic"; 631 | PRODUCT_NAME = "$(TARGET_NAME)"; 632 | SWIFT_SWIFT3_OBJC_INFERENCE = Off; 633 | SWIFT_VERSION = 4.0; 634 | }; 635 | name = Debug; 636 | }; 637 | 752543271D805F9D00C457A1 /* Release */ = { 638 | isa = XCBuildConfiguration; 639 | buildSettings = { 640 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 641 | DEVELOPMENT_TEAM = 6UNHY8UY6E; 642 | INFOPLIST_FILE = "MVVM-Generic/Info.plist"; 643 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 644 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 645 | PRODUCT_BUNDLE_IDENTIFIER = "com.ivanmagda.MVVM-Generic"; 646 | PRODUCT_NAME = "$(TARGET_NAME)"; 647 | SWIFT_SWIFT3_OBJC_INFERENCE = Off; 648 | SWIFT_VERSION = 4.0; 649 | }; 650 | name = Release; 651 | }; 652 | 75DAAD7B1D800CDB00BAC0FE /* Debug */ = { 653 | isa = XCBuildConfiguration; 654 | buildSettings = { 655 | ALWAYS_SEARCH_USER_PATHS = NO; 656 | CLANG_ANALYZER_NONNULL = YES; 657 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 658 | CLANG_CXX_LIBRARY = "libc++"; 659 | CLANG_ENABLE_MODULES = YES; 660 | CLANG_ENABLE_OBJC_ARC = YES; 661 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 662 | CLANG_WARN_BOOL_CONVERSION = YES; 663 | CLANG_WARN_COMMA = YES; 664 | CLANG_WARN_CONSTANT_CONVERSION = YES; 665 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 666 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 667 | CLANG_WARN_EMPTY_BODY = YES; 668 | CLANG_WARN_ENUM_CONVERSION = YES; 669 | CLANG_WARN_INFINITE_RECURSION = YES; 670 | CLANG_WARN_INT_CONVERSION = YES; 671 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 672 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 673 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 674 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 675 | CLANG_WARN_STRICT_PROTOTYPES = YES; 676 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 677 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 678 | CLANG_WARN_UNREACHABLE_CODE = YES; 679 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 680 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 681 | COPY_PHASE_STRIP = NO; 682 | DEBUG_INFORMATION_FORMAT = dwarf; 683 | ENABLE_STRICT_OBJC_MSGSEND = YES; 684 | ENABLE_TESTABILITY = YES; 685 | GCC_C_LANGUAGE_STANDARD = gnu99; 686 | GCC_DYNAMIC_NO_PIC = NO; 687 | GCC_NO_COMMON_BLOCKS = YES; 688 | GCC_OPTIMIZATION_LEVEL = 0; 689 | GCC_PREPROCESSOR_DEFINITIONS = ( 690 | "DEBUG=1", 691 | "$(inherited)", 692 | ); 693 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 694 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 695 | GCC_WARN_UNDECLARED_SELECTOR = YES; 696 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 697 | GCC_WARN_UNUSED_FUNCTION = YES; 698 | GCC_WARN_UNUSED_VARIABLE = YES; 699 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 700 | MTL_ENABLE_DEBUG_INFO = YES; 701 | ONLY_ACTIVE_ARCH = YES; 702 | SDKROOT = iphoneos; 703 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 704 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 705 | TARGETED_DEVICE_FAMILY = "1,2"; 706 | }; 707 | name = Debug; 708 | }; 709 | 75DAAD7C1D800CDB00BAC0FE /* Release */ = { 710 | isa = XCBuildConfiguration; 711 | buildSettings = { 712 | ALWAYS_SEARCH_USER_PATHS = NO; 713 | CLANG_ANALYZER_NONNULL = YES; 714 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 715 | CLANG_CXX_LIBRARY = "libc++"; 716 | CLANG_ENABLE_MODULES = YES; 717 | CLANG_ENABLE_OBJC_ARC = YES; 718 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 719 | CLANG_WARN_BOOL_CONVERSION = YES; 720 | CLANG_WARN_COMMA = YES; 721 | CLANG_WARN_CONSTANT_CONVERSION = YES; 722 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 723 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 724 | CLANG_WARN_EMPTY_BODY = YES; 725 | CLANG_WARN_ENUM_CONVERSION = YES; 726 | CLANG_WARN_INFINITE_RECURSION = YES; 727 | CLANG_WARN_INT_CONVERSION = YES; 728 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 729 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 730 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 731 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 732 | CLANG_WARN_STRICT_PROTOTYPES = YES; 733 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 734 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 735 | CLANG_WARN_UNREACHABLE_CODE = YES; 736 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 737 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 738 | COPY_PHASE_STRIP = NO; 739 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 740 | ENABLE_NS_ASSERTIONS = NO; 741 | ENABLE_STRICT_OBJC_MSGSEND = YES; 742 | GCC_C_LANGUAGE_STANDARD = gnu99; 743 | GCC_NO_COMMON_BLOCKS = YES; 744 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 745 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 746 | GCC_WARN_UNDECLARED_SELECTOR = YES; 747 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 748 | GCC_WARN_UNUSED_FUNCTION = YES; 749 | GCC_WARN_UNUSED_VARIABLE = YES; 750 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 751 | MTL_ENABLE_DEBUG_INFO = NO; 752 | SDKROOT = iphoneos; 753 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 754 | TARGETED_DEVICE_FAMILY = "1,2"; 755 | VALIDATE_PRODUCT = YES; 756 | }; 757 | name = Release; 758 | }; 759 | 75DAAD7E1D800CDB00BAC0FE /* Debug */ = { 760 | isa = XCBuildConfiguration; 761 | buildSettings = { 762 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 763 | DEVELOPMENT_TEAM = 6UNHY8UY6E; 764 | INFOPLIST_FILE = "MVVM-Example/Info.plist"; 765 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 766 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 767 | PRODUCT_BUNDLE_IDENTIFIER = "com.ivanmagda.MVVM-Example"; 768 | PRODUCT_NAME = "$(TARGET_NAME)"; 769 | SWIFT_SWIFT3_OBJC_INFERENCE = Off; 770 | SWIFT_VERSION = 4.0; 771 | }; 772 | name = Debug; 773 | }; 774 | 75DAAD7F1D800CDB00BAC0FE /* Release */ = { 775 | isa = XCBuildConfiguration; 776 | buildSettings = { 777 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 778 | DEVELOPMENT_TEAM = 6UNHY8UY6E; 779 | INFOPLIST_FILE = "MVVM-Example/Info.plist"; 780 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 781 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 782 | PRODUCT_BUNDLE_IDENTIFIER = "com.ivanmagda.MVVM-Example"; 783 | PRODUCT_NAME = "$(TARGET_NAME)"; 784 | SWIFT_SWIFT3_OBJC_INFERENCE = Off; 785 | SWIFT_VERSION = 4.0; 786 | }; 787 | name = Release; 788 | }; 789 | /* End XCBuildConfiguration section */ 790 | 791 | /* Begin XCConfigurationList section */ 792 | 750F02291D82ACDC0046F808 /* Build configuration list for PBXNativeTarget "MVVM-Greeting" */ = { 793 | isa = XCConfigurationList; 794 | buildConfigurations = ( 795 | 750F02271D82ACDC0046F808 /* Debug */, 796 | 750F02281D82ACDC0046F808 /* Release */, 797 | ); 798 | defaultConfigurationIsVisible = 0; 799 | defaultConfigurationName = Release; 800 | }; 801 | 752543251D805F9D00C457A1 /* Build configuration list for PBXNativeTarget "MVVM-Generic" */ = { 802 | isa = XCConfigurationList; 803 | buildConfigurations = ( 804 | 752543261D805F9D00C457A1 /* Debug */, 805 | 752543271D805F9D00C457A1 /* Release */, 806 | ); 807 | defaultConfigurationIsVisible = 0; 808 | defaultConfigurationName = Release; 809 | }; 810 | 75DAAD661D800CDB00BAC0FE /* Build configuration list for PBXProject "MVVM-Example" */ = { 811 | isa = XCConfigurationList; 812 | buildConfigurations = ( 813 | 75DAAD7B1D800CDB00BAC0FE /* Debug */, 814 | 75DAAD7C1D800CDB00BAC0FE /* Release */, 815 | ); 816 | defaultConfigurationIsVisible = 0; 817 | defaultConfigurationName = Release; 818 | }; 819 | 75DAAD7D1D800CDB00BAC0FE /* Build configuration list for PBXNativeTarget "MVVM-Example" */ = { 820 | isa = XCConfigurationList; 821 | buildConfigurations = ( 822 | 75DAAD7E1D800CDB00BAC0FE /* Debug */, 823 | 75DAAD7F1D800CDB00BAC0FE /* Release */, 824 | ); 825 | defaultConfigurationIsVisible = 0; 826 | defaultConfigurationName = Release; 827 | }; 828 | /* End XCConfigurationList section */ 829 | }; 830 | rootObject = 75DAAD631D800CDB00BAC0FE /* Project object */; 831 | } 832 | -------------------------------------------------------------------------------- /MVVM-Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MVVM-Example.xcodeproj/xcuserdata/ivanmagda.xcuserdatad/xcschemes/MVVM-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /MVVM-Example.xcodeproj/xcuserdata/ivanmagda.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | MVVM-Example.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | MVVM-Generic.xcscheme 13 | 14 | orderHint 15 | 1 16 | 17 | MVVM-Greeting.xcscheme 18 | 19 | orderHint 20 | 2 21 | 22 | 23 | SuppressBuildableAutocreation 24 | 25 | 750F02171D82ACDC0046F808 26 | 27 | primary 28 | 29 | 30 | 752543151D805F9D00C457A1 31 | 32 | primary 33 | 34 | 35 | 75DAAD6A1D800CDB00BAC0FE 36 | 37 | primary 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /MVVM-Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Ivan Magda 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import UIKit 24 | 25 | // MARK: AppDelegate: UIResponder, UIApplicationDelegate 26 | 27 | @UIApplicationMain 28 | class AppDelegate: UIResponder, UIApplicationDelegate { 29 | 30 | // MARK: Instance Variables 31 | 32 | var window: UIWindow? 33 | 34 | // MARK: UIApplicationDelegate 35 | 36 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 37 | return true 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /MVVM-Example/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 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /MVVM-Example/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /MVVM-Example/Assets.xcassets/minion.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "minion.jpg", 6 | "scale" : "1x" 7 | } 8 | ], 9 | "info" : { 10 | "version" : 1, 11 | "author" : "xcode" 12 | } 13 | } -------------------------------------------------------------------------------- /MVVM-Example/Assets.xcassets/minion.imageset/minion.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivan-magda/MVVM-Example/026e8d286ca0ad0cb40cbae6fb2957837556b8b0/MVVM-Example/Assets.xcassets/minion.imageset/minion.jpg -------------------------------------------------------------------------------- /MVVM-Example/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 | 27 | 28 | -------------------------------------------------------------------------------- /MVVM-Example/Base.lproj/Main.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 | 27 | 28 | 29 | 30 | 31 | 32 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /MVVM-Example/CaseCountable.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Ivan Magda 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import Foundation 24 | 25 | protocol CaseCountable { 26 | static func countCases() -> Int 27 | static var caseCount: Int { get } 28 | } 29 | 30 | extension CaseCountable where Self: RawRepresentable, Self.RawValue == Int { 31 | static func countCases() -> Int { 32 | var count = 0 33 | while let _ = Self(rawValue: count) { count += 1 } 34 | return count 35 | } 36 | 37 | static var caseCount: Int { 38 | return countCases() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /MVVM-Example/CellReuseIdentifierlabel.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Ivan Magda 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import Foundation 24 | 25 | protocol CellReuseIdentifierabel { 26 | static var reuseIdentifier: String { get } 27 | } 28 | 29 | extension CellReuseIdentifierabel { 30 | static var reuseIdentifier: String { 31 | return "\(self)" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /MVVM-Example/DetailTextPresentable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailTextPresentable.swift 3 | // MVVM-Example 4 | // 5 | // Created by Ivan Magda on 08/09/16. 6 | // Copyright © 2016 Ivan Magda. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol DetailTextPresentable: TextPresentable { 12 | var detailText: String { get } 13 | var detailTextColor: UIColor { get } 14 | var detailFont: UIFont { get } 15 | } 16 | 17 | extension DetailTextPresentable { 18 | 19 | var detailTextColor: UIColor { 20 | return .lightGray 21 | } 22 | 23 | var detailFont: UIFont { 24 | return .systemFont(ofSize: 14) 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /MVVM-Example/ImagePresentable.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Ivan Magda 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import UIKit 24 | 25 | protocol ImagePresentable { 26 | var imageName: String? { get } 27 | var imageURL: URL? { get } 28 | } 29 | 30 | extension ImagePresentable { 31 | var imageURL: URL? { return nil } 32 | } 33 | -------------------------------------------------------------------------------- /MVVM-Example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 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 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /MVVM-Example/MinionModeViewModel.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Ivan Magda 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import UIKit 24 | 25 | struct MinionModeViewModel { 26 | } 27 | 28 | // MARK: MinionModeViewModel: TextPresentable 29 | 30 | extension MinionModeViewModel: TextPresentable { 31 | 32 | var text: String { return "Minion Mode" } 33 | 34 | var textColor: UIColor { return .black } 35 | 36 | var font: UIFont { return .systemFont(ofSize: 17.0) } 37 | 38 | } 39 | 40 | // MARK: MinionModeViewModel: SwitchPresentable 41 | 42 | extension MinionModeViewModel: SwitchPresentable { 43 | 44 | var switchOn: Bool { return true } 45 | var switchColor: UIColor { return .yellow } 46 | 47 | func onSwitchToggleOn(on: Bool) { 48 | if on { 49 | print("The minions are here to stay!!!") 50 | } else { 51 | print("The minions went out to play!") 52 | } 53 | } 54 | 55 | } 56 | 57 | // MARK: MinionModeViewModel: ImagePresentable 58 | 59 | extension MinionModeViewModel: ImagePresentable { 60 | var imageName: String? { 61 | return "minion" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /MVVM-Example/SettingsViewController.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Ivan Magda 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import UIKit 24 | 25 | // MARK: ViewController: UIViewController 26 | 27 | class SettingsViewController: UIViewController { 28 | 29 | // MARK: Variables 30 | 31 | @IBOutlet weak var tableView: UITableView! 32 | 33 | private let tableViewDataSource = SettingsViewControllerTableViewDataSource() 34 | 35 | // MARK: Life Cycle 36 | 37 | override func viewDidLoad() { 38 | super.viewDidLoad() 39 | configure() 40 | } 41 | 42 | private func configure() { 43 | tableView.dataSource = tableViewDataSource 44 | tableView.delegate = self 45 | 46 | tableView.backgroundColor = UIColor(colorLiteralRed: 243.0 / 255.0, green: 228.0 / 255.0, 47 | blue: 200.0 / 255.0, alpha: 1.0) 48 | } 49 | 50 | } 51 | 52 | // MARK: - ViewController: UITableViewDelegate - 53 | 54 | extension SettingsViewController: UITableViewDelegate { 55 | 56 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 57 | tableView.deselectRow(at: indexPath, animated: true) 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /MVVM-Example/SettingsViewControllerTableViewDataSource.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Ivan Magda 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import UIKit 24 | 25 | class SettingsViewControllerTableViewDataSource: NSObject { 26 | 27 | enum Setting: Int, CaseCountable { 28 | case MinionMode 29 | } 30 | 31 | } 32 | 33 | extension SettingsViewControllerTableViewDataSource: UITableViewDataSource { 34 | 35 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 36 | return Setting.caseCount 37 | } 38 | 39 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 40 | switch Setting(rawValue: indexPath.row)! { 41 | case .MinionMode: 42 | let identifier = SwitchWithTextTableViewCell.reuseIdentifier 43 | let cell = tableView.dequeueReusableCell(withIdentifier: identifier) as! SwitchWithTextTableViewCell 44 | cell.configure(withDelegate: MinionModeViewModel()) 45 | 46 | return cell 47 | } 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /MVVM-Example/SwitchPresentable.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Ivan Magda 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import UIKit 24 | 25 | protocol SwitchPresentable { 26 | var switchOn: Bool { get } 27 | var switchColor: UIColor { get } 28 | 29 | func onSwitchToggleOn(on: Bool) 30 | } 31 | -------------------------------------------------------------------------------- /MVVM-Example/SwitchWithTextTableViewCell.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Ivan Magda 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import UIKit 24 | 25 | typealias SwitchWithTextAndImageViewPresentable = TextPresentable & SwitchPresentable & ImagePresentable 26 | 27 | // MARK: - SwitchWithTextTableViewCell: UITableViewCell 28 | 29 | class SwitchWithTextTableViewCell: UITableViewCell { 30 | 31 | // MARK: Outlets 32 | 33 | @IBOutlet weak var label: UILabel! 34 | @IBOutlet weak var switchToggle: UISwitch! 35 | @IBOutlet weak var imageV: UIImageView! 36 | 37 | // MARK: Private Variables 38 | 39 | private var delegate: SwitchWithTextAndImageViewPresentable? 40 | 41 | // MARK: Public 42 | 43 | func configure(withDelegate delegate: SwitchWithTextAndImageViewPresentable) { 44 | self.delegate = delegate 45 | 46 | backgroundColor = .clear 47 | 48 | label.text = delegate.text 49 | label.textColor = delegate.textColor 50 | label.font = delegate.font 51 | 52 | switchToggle.isOn = delegate.switchOn 53 | switchToggle.onTintColor = delegate.switchColor 54 | 55 | if let imageName = delegate.imageName { 56 | imageV.image = UIImage(named: imageName) 57 | } 58 | } 59 | 60 | // MARK: Actions 61 | 62 | @IBAction func onSwitchToggle(_ sender: UISwitch) { 63 | delegate?.onSwitchToggleOn(on: sender.isOn) 64 | } 65 | 66 | } 67 | 68 | // MARK: - SwitchWithTextTableViewCell: CellReuseIdentifierlabel - 69 | 70 | extension SwitchWithTextTableViewCell: CellReuseIdentifierabel { 71 | } 72 | -------------------------------------------------------------------------------- /MVVM-Example/TextPresentable.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Ivan Magda 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import UIKit 24 | 25 | protocol TextPresentable { 26 | var text: String { get } 27 | var textColor: UIColor { get } 28 | var font: UIFont { get } 29 | } 30 | 31 | extension TextPresentable { 32 | 33 | var textColor: UIColor { 34 | return .black 35 | } 36 | 37 | var font: UIFont { 38 | return .systemFont(ofSize: 17) 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /MVVM-Generic/App.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Ivan Magda 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import Foundation 24 | 25 | struct App { 26 | var name: String 27 | var summary: String 28 | var category: String 29 | var imageURL: URL 30 | } 31 | 32 | extension App { 33 | static func all(_ completion: @escaping ([App]?) -> ()) { 34 | DataManager.jsonForResource("topapps") { json in 35 | completion(AppBuilder.apps(from: json ?? [:])) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /MVVM-Generic/AppBuilder.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Ivan Magda 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import Foundation 24 | 25 | class AppBuilder { 26 | 27 | // MARK: Static 28 | 29 | static let shared = AppBuilder() 30 | 31 | // MARK: Init 32 | 33 | private init() { 34 | } 35 | 36 | // MARK: Parsing 37 | 38 | class func apps(from json: JSONDictionary) -> [App]? { 39 | guard let feed = json["feed"] as? JSONDictionary, 40 | let apps = feed["entry"] as? [JSONDictionary] else { return nil } 41 | 42 | return apps.flatMap { app(from: $0) } 43 | } 44 | 45 | class func app(from json: JSONDictionary) -> App? { 46 | guard let nameContainer = json["im:name"] as? JSONDictionary, 47 | let name = nameContainer["label"] as? String else { return nil } 48 | 49 | guard let summaryContainer = json["summary"] as? JSONDictionary, 50 | let summary = summaryContainer["label"] as? String else { return nil } 51 | 52 | guard let categoryContainer = json["category"] as? JSONDictionary, 53 | let attributes = categoryContainer["attributes"] as? JSONDictionary, 54 | let category = attributes["label"] as? String else { return nil } 55 | 56 | guard let images = json["im:image"] as? [JSONDictionary], 57 | let image = images.first?["label"] as? String, 58 | let imageURL = URL(string: image) else { return nil } 59 | 60 | return App(name: name, summary: summary, category: category, imageURL: imageURL) 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /MVVM-Generic/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Ivan Magda 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import UIKit 24 | 25 | // MARK: AppDelegate: UIResponder, UIApplicationDelegate 26 | 27 | @UIApplicationMain 28 | class AppDelegate: UIResponder, UIApplicationDelegate { 29 | 30 | // MARK: Instance Variables 31 | 32 | var window: UIWindow? 33 | var app: AppDirector? 34 | 35 | // MARK: UIApplicationDelegate 36 | 37 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 38 | 39 | if let window = window { 40 | app = AppDirector(window: window) 41 | } 42 | 43 | return true 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /MVVM-Generic/AppDetailViewController.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Ivan Magda 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import UIKit 24 | 25 | class AppDetailViewController: UIViewController { 26 | 27 | // MARK: Outlets 28 | 29 | @IBOutlet weak var header: UILabel! 30 | @IBOutlet weak var summary: UILabel! 31 | 32 | // MARK: Variables 33 | 34 | var app: App! 35 | 36 | // MARK: Life Cycle 37 | 38 | override func viewDidLoad() { 39 | super.viewDidLoad() 40 | 41 | title = "Detail" 42 | 43 | header.text = app.name 44 | summary.text = app.summary 45 | } 46 | 47 | override func viewDidLayoutSubviews() { 48 | super.viewDidLayoutSubviews() 49 | 50 | if let containerView = header.superview { 51 | NSLayoutConstraint.activate([ 52 | containerView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 1.0) 53 | ]) 54 | } 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /MVVM-Generic/AppDirector.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Ivan Magda 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import UIKit 24 | 25 | class AppDirector { 26 | 27 | // MARK: Inastance Variables 28 | 29 | private let window: UIWindow 30 | private let storyboard = UIStoryboard(name: "Main", bundle: nil) 31 | private let navigationController: UINavigationController 32 | 33 | // MARK: Init 34 | 35 | init(window: UIWindow) { 36 | self.window = window 37 | self.navigationController = window.rootViewController as! UINavigationController 38 | configure() 39 | } 40 | 41 | // MARK: Private 42 | 43 | private func configure() { 44 | let appsVC = ItemsViewController(items: [App](), 45 | configure: { (cell: AppTableViewCell, app) in 46 | cell.configure(withDelegate: AppTableCellViewModel(app: app)) 47 | }) 48 | appsVC.didSelect = showApp 49 | appsVC.title = "Top Apps" 50 | 51 | navigationController.viewControllers = [appsVC] 52 | 53 | weak var weakApps = appsVC 54 | App.all { apps in 55 | weakApps?.items = apps ?? [App]() 56 | } 57 | } 58 | 59 | private func showApp(_ app: App) { 60 | let detailVC = storyboard.instantiateViewController(withIdentifier: "Detail") as! AppDetailViewController 61 | detailVC.app = app 62 | navigationController.pushViewController(detailVC, animated: true) 63 | } 64 | 65 | } 66 | 67 | -------------------------------------------------------------------------------- /MVVM-Generic/AppTableCellViewModel.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Ivan Magda 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import UIKit 24 | 25 | // MARK: AppTableCellViewModel 26 | 27 | struct AppTableCellViewModel { 28 | var app: App 29 | } 30 | 31 | // MARK: AppTableCellViewModel: ImagePresentable 32 | 33 | extension AppTableCellViewModel: ImagePresentable { 34 | 35 | var imageURL: URL? { 36 | return app.imageURL 37 | } 38 | 39 | var imageName: String? { 40 | return nil 41 | } 42 | 43 | } 44 | 45 | // MARK: AppTableCellViewModel: TextPresentable 46 | 47 | extension AppTableCellViewModel: TextPresentable { 48 | 49 | var text: String { 50 | return app.name.capitalized 51 | } 52 | 53 | var textColor: UIColor { 54 | return .black 55 | } 56 | 57 | var font: UIFont { 58 | return .systemFont(ofSize: 17.0) 59 | } 60 | 61 | } 62 | 63 | // MARK: AppTableCellViewModel: DetailTextPresentable 64 | 65 | extension AppTableCellViewModel: DetailTextPresentable { 66 | 67 | var detailText: String { 68 | return app.category.capitalized 69 | } 70 | 71 | var detailTextColor: UIColor { 72 | return .lightGray 73 | } 74 | 75 | var detailFont: UIFont { 76 | return .systemFont(ofSize: UIFont.smallSystemFontSize) 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /MVVM-Generic/AppTableViewCell.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Ivan Magda 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import UIKit 24 | 25 | class AppTableViewCell: UITableViewCell where T: TextPresentable, T: DetailTextPresentable, T: ImagePresentable { 26 | 27 | // MARK: Variables 28 | 29 | private lazy var appImageView : UIImageView = { 30 | let iv = UIImageView() 31 | iv.translatesAutoresizingMaskIntoConstraints = false 32 | iv.clipsToBounds = true 33 | iv.contentMode = .scaleToFill 34 | 35 | return iv 36 | }() 37 | 38 | private lazy var appTitleLabel : UILabel = { 39 | let label = UILabel() 40 | label.translatesAutoresizingMaskIntoConstraints = false 41 | label.textAlignment = .left 42 | 43 | return label 44 | }() 45 | 46 | private lazy var appDetailLabel : UILabel = { 47 | let label = UILabel() 48 | label.translatesAutoresizingMaskIntoConstraints = false 49 | label.textAlignment = .right 50 | 51 | return label 52 | }() 53 | 54 | // MARK: Init 55 | 56 | override init(style: UITableViewCellStyle, reuseIdentifier: String?) { 57 | super.init(style: style, reuseIdentifier: reuseIdentifier) 58 | sharedInitialization() 59 | } 60 | 61 | required init?(coder aDecoder: NSCoder) { 62 | fatalError("init(coder:) has not been implemented") 63 | } 64 | 65 | override func prepareForReuse() { 66 | super.prepareForReuse() 67 | appImageView.image = nil 68 | } 69 | 70 | // MARK: Public 71 | 72 | func configure(withDelegate delegate: T) { 73 | if let imageURL = delegate.imageURL { 74 | ImageDownloader.load(with: imageURL, completion: { [weak self] in 75 | self?.appImageView.image = $0 76 | }) 77 | } 78 | 79 | appTitleLabel.text = delegate.text 80 | appTitleLabel.textColor = delegate.textColor 81 | appTitleLabel.font = delegate.font 82 | 83 | appDetailLabel.text = delegate.detailText 84 | appDetailLabel.textColor = delegate.detailTextColor 85 | appDetailLabel.font = delegate.detailFont 86 | } 87 | 88 | // MARK: Private 89 | 90 | private func sharedInitialization() { 91 | addSubview(appImageView) 92 | addSubview(appTitleLabel) 93 | addSubview(appDetailLabel) 94 | 95 | // Image view constraints. 96 | NSLayoutConstraint.activate([ 97 | appImageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 15), 98 | appImageView.centerYAnchor.constraint(equalTo: layoutMarginsGuide.centerYAnchor), 99 | appImageView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.75), 100 | appImageView.widthAnchor.constraint(equalTo: appImageView.heightAnchor)] 101 | ) 102 | 103 | // Title label constraints. 104 | NSLayoutConstraint.activate([ 105 | appTitleLabel.leadingAnchor.constraint(equalTo: appImageView.trailingAnchor, constant: 8), 106 | appTitleLabel.centerYAnchor.constraint(equalTo: centerYAnchor)] 107 | ) 108 | 109 | NSLayoutConstraint.activate([ 110 | appDetailLabel.leadingAnchor.constraint(greaterThanOrEqualTo: appTitleLabel.trailingAnchor, constant: 8), 111 | appDetailLabel.centerYAnchor.constraint(equalTo: appTitleLabel.centerYAnchor), 112 | appDetailLabel.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor, constant: 8)] 113 | ) 114 | 115 | appTitleLabel.setContentCompressionResistancePriority(UILayoutPriority.defaultLow, for: .horizontal) 116 | appDetailLabel.setContentCompressionResistancePriority(UILayoutPriority.defaultHigh, for: .horizontal) 117 | appImageView.setContentCompressionResistancePriority(UILayoutPriority.defaultHigh, for: .horizontal) 118 | appImageView.setContentCompressionResistancePriority(UILayoutPriority.defaultHigh, for: .vertical) 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /MVVM-Generic/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /MVVM-Generic/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 | 27 | 28 | -------------------------------------------------------------------------------- /MVVM-Generic/Base.lproj/Main.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 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 45 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /MVVM-Generic/DataManager.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Ivan Magda 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import Foundation 24 | 25 | struct DataManager { 26 | 27 | static func dataForResource(_ name: String, completion: @escaping (Data?) -> ()) { 28 | doInBackground { 29 | guard let filePath = Bundle.main.url(forResource: name, withExtension: "json") else { 30 | completion(nil) 31 | return 32 | } 33 | 34 | let data = try? Data(contentsOf: filePath, options: .uncached) 35 | 36 | doOnMain { 37 | completion(data) 38 | } 39 | } 40 | } 41 | 42 | static func jsonForResource(_ name: String, completion: @escaping (_ json: JSONDictionary?) -> ()) { 43 | dataForResource(name) { completion(parseData($0)) } 44 | } 45 | 46 | private static func parseData(_ data: Data?) -> JSONDictionary? { 47 | guard let data = data else { return nil } 48 | let jsonObject = try? JSONSerialization.jsonObject(with: data, options: []) 49 | return jsonObject.flatMap { $0 as? JSONDictionary } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /MVVM-Generic/GCDUtils.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Ivan Magda 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import Foundation 24 | 25 | func doInBackground(_ block: @escaping () -> ()) { 26 | DispatchQueue.global(qos: .default).async { 27 | block() 28 | } 29 | } 30 | 31 | func doOnMain(_ block: @escaping () -> ()) { 32 | DispatchQueue.main.async { 33 | block() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /MVVM-Generic/ImageDownloader.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Ivan Magda 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import UIKit 24 | 25 | class ImageDownloader { 26 | 27 | private init() { 28 | } 29 | 30 | static func load(with URL: URL, completion: @escaping (_ image: UIImage?) -> ()) { 31 | URLSession.shared.dataTask(with: URL) { (data, _, _) in 32 | let image = data.flatMap { UIImage(data: $0) } 33 | doOnMain { completion(image) } 34 | }.resume() 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /MVVM-Generic/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSAppTransportSecurity 6 | 7 | NSAllowsArbitraryLoads 8 | 9 | 10 | CFBundleDevelopmentRegion 11 | en 12 | CFBundleExecutable 13 | $(EXECUTABLE_NAME) 14 | CFBundleIdentifier 15 | $(PRODUCT_BUNDLE_IDENTIFIER) 16 | CFBundleInfoDictionaryVersion 17 | 6.0 18 | CFBundleName 19 | $(PRODUCT_NAME) 20 | CFBundlePackageType 21 | APPL 22 | CFBundleShortVersionString 23 | 1.0 24 | CFBundleVersion 25 | 1 26 | LSRequiresIPhoneOS 27 | 28 | UILaunchStoryboardName 29 | LaunchScreen 30 | UIMainStoryboardFile 31 | Main 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /MVVM-Generic/ItemsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ItemsViewController.swift 3 | // MVVM-Example 4 | // 5 | // Created by Ivan Magda on 08/09/16. 6 | // Copyright © 2016 Ivan Magda. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /*** 12 | * Source code from this talk: https://talk.objc.io/episodes/S01E06-generic-table-view-controllers 13 | */ 14 | 15 | final class ItemsViewController: UITableViewController { 16 | 17 | // MARK: Variables 18 | 19 | var items: [Item] = [] { 20 | didSet { 21 | tableView.reloadData() 22 | } 23 | } 24 | let reuseIdentifier = "Cell" 25 | 26 | let configure: (Cell, Item) -> () 27 | var didSelect: (Item) -> () = { _ in } 28 | 29 | // MARK: Init 30 | 31 | init(items: [Item], configure: @escaping (Cell, Item) -> ()) { 32 | self.configure = configure 33 | super.init(style: .plain) 34 | self.items = items 35 | } 36 | 37 | required init?(coder aDecoder: NSCoder) { 38 | fatalError("init(coder:) has not been implemented") 39 | } 40 | 41 | // MARK: Life Cycle 42 | 43 | override func viewDidLoad() { 44 | tableView.register(Cell.self, forCellReuseIdentifier: reuseIdentifier) 45 | } 46 | 47 | // MARK: Delegate 48 | 49 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 50 | let item = items[indexPath.row] 51 | didSelect(item) 52 | } 53 | 54 | // MARK: Data Source 55 | 56 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 57 | return items.count 58 | } 59 | 60 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 61 | let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) as! Cell 62 | let item = items[indexPath.row] 63 | configure(cell, item) 64 | return cell 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /MVVM-Generic/Utils.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Ivan Magda 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import Foundation 24 | 25 | typealias JSONDictionary = [String: AnyObject] 26 | 27 | extension String: Error { 28 | } 29 | -------------------------------------------------------------------------------- /MVVM-Greeting/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // MVVM-Greeting 4 | // 5 | // Created by Ivan Magda on 09/09/2016. 6 | // Copyright © 2016 Ivan Magda. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /MVVM-Greeting/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // MVVM-Greeting 4 | // 5 | // Created by Ivan Magda on 09/09/2016. 6 | // Copyright © 2016 Ivan Magda. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | 24 | - (void)applicationWillResignActive:(UIApplication *)application { 25 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 26 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 27 | } 28 | 29 | 30 | - (void)applicationDidEnterBackground:(UIApplication *)application { 31 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | 36 | - (void)applicationWillEnterForeground:(UIApplication *)application { 37 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 38 | } 39 | 40 | 41 | - (void)applicationDidBecomeActive:(UIApplication *)application { 42 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 43 | } 44 | 45 | 46 | - (void)applicationWillTerminate:(UIApplication *)application { 47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 48 | } 49 | 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /MVVM-Greeting/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Ivan Magda 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import UIKit 24 | 25 | @UIApplicationMain 26 | class AppDelegate: UIResponder, UIApplicationDelegate { 27 | 28 | var window: UIWindow? 29 | 30 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 31 | return true 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /MVVM-Greeting/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /MVVM-Greeting/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 | 27 | 28 | -------------------------------------------------------------------------------- /MVVM-Greeting/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /MVVM-Greeting/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 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 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /MVVM-Greeting/Person.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Ivan Magda 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | #import 24 | 25 | @interface Person : NSObject 26 | 27 | - (instancetype)initWithSalutation:(NSString *)salutation firstName:(NSString *)firstName 28 | lastName:(NSString *)lastName birthdate:(NSDate *)birthdate; 29 | 30 | @property (nonatomic, readonly) NSString *salutation; 31 | @property (nonatomic, readonly) NSString *firstName; 32 | @property (nonatomic, readonly) NSString *lastName; 33 | @property (nonatomic, readonly) NSDate *birthdate; 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /MVVM-Greeting/Person.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Ivan Magda 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | #import "Person.h" 24 | 25 | @implementation Person 26 | 27 | - (instancetype)initWithSalutation:(NSString *)salutation firstName:(NSString *)firstName lastName:(NSString *)lastName birthdate:(NSDate *)birthdate { 28 | self = [super init]; 29 | if (!self) return nil; 30 | 31 | _salutation = salutation; 32 | _firstName = firstName; 33 | _lastName = lastName; 34 | _birthdate = birthdate; 35 | 36 | return self; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /MVVM-Greeting/Person.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Ivan Magda 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import Foundation 24 | 25 | struct Person { 26 | var salutation: String 27 | var firstName: String 28 | var lastName: String 29 | var birthdate: Date 30 | } 31 | 32 | extension Person { 33 | 34 | static func steveJobs() -> Person { 35 | var birthdateComponents = DateComponents() 36 | birthdateComponents.year = 1955 37 | birthdateComponents.month = 2 38 | birthdateComponents.day = 24 39 | let birthdate = Calendar.current.date(from: birthdateComponents)! 40 | 41 | return Person(salutation: "Mr.", firstName: "Steve", lastName: "Jobs", birthdate: birthdate) 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /MVVM-Greeting/PersonViewController.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Ivan Magda 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | #import 24 | 25 | @interface PersonViewController : UIViewController 26 | 27 | 28 | @end 29 | 30 | -------------------------------------------------------------------------------- /MVVM-Greeting/PersonViewController.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Ivan Magda 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | #import "PersonViewController.h" 24 | 25 | @interface PersonViewController () 26 | 27 | @end 28 | 29 | @implementation PersonViewController 30 | 31 | - (void)viewDidLoad { 32 | [super viewDidLoad]; 33 | } 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /MVVM-Greeting/PersonViewController.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Ivan Magda 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import UIKit 24 | 25 | // MARK: PersonViewController: UIViewController 26 | 27 | class PersonViewController: UIViewController { 28 | 29 | // MARK: Outlets 30 | 31 | @IBOutlet weak var nameTextLabel: UILabel! 32 | @IBOutlet weak var birthdateTextLabel: UILabel! 33 | 34 | // MARK: Instance Variables 35 | 36 | fileprivate let personViewModel = PersonViewModel(person: Person.steveJobs()) 37 | 38 | // MARK: Life Cycle 39 | 40 | override func viewDidLoad() { 41 | super.viewDidLoad() 42 | configure() 43 | } 44 | 45 | } 46 | 47 | // MARK: PersonViewController (Configure) 48 | 49 | extension PersonViewController { 50 | /// Configures the UI. 51 | fileprivate func configure() { 52 | nameTextLabel.text = personViewModel.nameText 53 | birthdateTextLabel.text = personViewModel.birthdateText 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /MVVM-Greeting/PersonViewModel.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Ivan Magda 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | #import 24 | 25 | @interface PersonViewModel : NSObject 26 | 27 | -(instancetype)initWithPerson:(Person *)person; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /MVVM-Greeting/PersonViewModel.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Ivan Magda 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | #import "PersonViewModel.h" 24 | 25 | @implementation PersonViewModel 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /MVVM-Greeting/PersonViewModel.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Ivan Magda 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import Foundation 24 | 25 | struct PersonViewModel { 26 | var person: Person 27 | } 28 | 29 | extension PersonViewModel { 30 | 31 | var nameText: String { 32 | if person.salutation.characters.count > 0 { 33 | return "\(person.salutation) \(person.firstName) \(person.lastName)" 34 | } else { 35 | return "\(person.firstName) \(person.lastName)" 36 | } 37 | } 38 | 39 | var birthdateText: String { 40 | return PersonViewModel.dateFormatter.string(from: person.birthdate) 41 | } 42 | 43 | fileprivate static var dateFormatter: DateFormatter { 44 | let formatter = DateFormatter() 45 | formatter.dateStyle = .full 46 | return formatter 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /MVVM-Greeting/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // MVVM-Greeting 4 | // 5 | // Created by Ivan Magda on 09/09/2016. 6 | // Copyright © 2016 Ivan Magda. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MVVM-Example 2 | 3 | [![codebeat badge](https://codebeat.co/badges/75daa704-de95-4880-8f95-c3a6812e7429)](https://codebeat.co/projects/github-com-vanyaland-mvvm-example-master) 4 | 5 | Protocol-Oriented MVVM example apps. 6 | 7 | Sample projects: 8 | * `MVVM-Example` - A Settings screen that has one settings – put your app in Minion Mode! 9 | * `MVVM-Generic` - A Top Apps application, that presents fetched apps using generics approach. 10 | * `MVVM-Greeting` - A person greeting app, shows name and birthdate. 11 | 12 | ## Instructions 13 | Open `MVVM-Example.xcodeproj`, select desired target, build and run, enjoy! 14 | 15 | ## Greate Blog Posts 16 | * [Natasha Murashev](https://www.natashatherobot.com/), ["Introduction to Protocol-Oriented MVVM"](https://realm.io/news/doios-natasha-murashev-protocol-oriented-mvvm/) 17 | * [Ash Furrow](http://artsy.github.io/), ["MVVM in Swift"](http://artsy.github.io/blog/2015/09/24/mvvm-in-swift/) 18 | * [objc.io](https://www.objc.io/), ["Intro to MVVM"](https://www.objc.io/issues/13-architecture/mvvm/) 19 | 20 | ## Screenshots 21 | 22 | 24 | 26 | 27 | 29 | 31 | -------------------------------------------------------------------------------- /resources/images/mvvm-example-minion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivan-magda/MVVM-Example/026e8d286ca0ad0cb40cbae6fb2957837556b8b0/resources/images/mvvm-example-minion.png -------------------------------------------------------------------------------- /resources/images/mvvm-generic-apps-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivan-magda/MVVM-Example/026e8d286ca0ad0cb40cbae6fb2957837556b8b0/resources/images/mvvm-generic-apps-list.png -------------------------------------------------------------------------------- /resources/images/mvvm-generic-detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivan-magda/MVVM-Example/026e8d286ca0ad0cb40cbae6fb2957837556b8b0/resources/images/mvvm-generic-detail.png -------------------------------------------------------------------------------- /resources/images/mvvm-greeting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivan-magda/MVVM-Example/026e8d286ca0ad0cb40cbae6fb2957837556b8b0/resources/images/mvvm-greeting.png --------------------------------------------------------------------------------