├── architecture.jpg ├── MVP-Sample ├── Assets.xcassets │ ├── Contents.json │ ├── Shoes │ │ ├── Contents.json │ │ ├── 12732.imageset │ │ │ ├── 12732@3x.jpg │ │ │ └── Contents.json │ │ ├── 12746.imageset │ │ │ ├── 12746@3x.jpg │ │ │ └── Contents.json │ │ ├── 12837.imageset │ │ │ ├── 12837@3x.jpg │ │ │ └── Contents.json │ │ ├── 12841.imageset │ │ │ ├── 12841@3x.jpg │ │ │ └── Contents.json │ │ ├── 9Osfhjm.imageset │ │ │ ├── 9Osfhjm.jpg │ │ │ └── Contents.json │ │ ├── JwKauWO.imageset │ │ │ ├── JwKauWO.jpg │ │ │ └── Contents.json │ │ ├── N6OObvC.imageset │ │ │ ├── N6OObvC.jpg │ │ │ └── Contents.json │ │ ├── QRdx9Il.imageset │ │ │ ├── QRdx9Il.jpg │ │ │ └── Contents.json │ │ ├── TNOsGqn.imageset │ │ │ ├── TNOsGqn.jpg │ │ │ └── Contents.json │ │ ├── cQnmyKQ.imageset │ │ │ ├── cQnmyKQ.jpg │ │ │ └── Contents.json │ │ ├── mm6pknp.imageset │ │ │ ├── mm6pknp.jpg │ │ │ └── Contents.json │ │ ├── o0l47Ca.imageset │ │ │ ├── o0l47Ca.jpg │ │ │ └── Contents.json │ │ ├── p4Ud82Q.imageset │ │ │ ├── p4Ud82Q.jpg │ │ │ └── Contents.json │ │ ├── M29vcEQ0Ry5qcGc=.imageset │ │ │ ├── M29vcEQ0Ry5qcGc=.jpg │ │ │ └── Contents.json │ │ ├── MTE1MDZAM3guanBn.imageset │ │ │ ├── MTE1MDZAM3guanBn.jpg │ │ │ └── Contents.json │ │ ├── MTIxNjhAM3guanBn.imageset │ │ │ ├── MTIxNjhAM3guanBn.jpg │ │ │ └── Contents.json │ │ ├── RDdGVGV6eS5qcGc=.imageset │ │ │ ├── RDdGVGV6eS5qcGc=.jpg │ │ │ └── Contents.json │ │ ├── wp-contentuploads201903Nike-Cortez-Los-Angeles-CI9873-001-Release-Date-1.imageset │ │ │ ├── wp-contentuploads201903Nike-Cortez-Los-Angeles-CI9873-001-Release-Date-1.jpg │ │ │ └── Contents.json │ │ ├── wp-contentuploads201903Nike-Cortez-Los-Angeles-CI9873-400-Release-Date-1.imageset │ │ │ ├── wp-contentuploads201903Nike-Cortez-Los-Angeles-CI9873-400-Release-Date-1.jpg │ │ │ └── Contents.json │ │ ├── d3AtY29udGVudHVwbG9hZHMyMDE4MDdqb3JkYW4tbGVnYWN5LTMxMi1BUTQxNjAtMzAxLTEuanBn.imageset │ │ │ ├── d3AtY29udGVudHVwbG9hZHMyMDE4MDdqb3JkYW4tbGVnYWN5LTMxMi1BUTQxNjAtMzAxLTEuanBn.jpg │ │ │ └── Contents.json │ │ ├── d3AtY29udGVudHVwbG9hZHMyMDE4MTFOaWtlLUt5cmllLTUtVGFjby1BTzI5MTgtOTAyLmpwZw==.imageset │ │ │ ├── d3AtY29udGVudHVwbG9hZHMyMDE4MTFOaWtlLUt5cmllLTUtVGFjby1BTzI5MTgtOTAyLmpwZw==.jpg │ │ │ └── Contents.json │ │ ├── wp-contentuploads201902nike-air-fear-of-god-180-black-AT8087-002-release-date-1.imageset │ │ │ ├── wp-contentuploads201902nike-air-fear-of-god-180-black-AT8087-002-release-date-1.jpg │ │ │ └── Contents.json │ │ ├── d3AtY29udGVudHVwbG9hZHMyMDE4MTFhaXItam9yZGFuLTQtZG8tdGhlLXJpZ2h0LXRoaW5nLTIuanBn.imageset │ │ │ ├── d3AtY29udGVudHVwbG9hZHMyMDE4MTFhaXItam9yZGFuLTQtZG8tdGhlLXJpZ2h0LXRoaW5nLTIuanBn.jpg │ │ │ └── Contents.json │ │ ├── d3AtY29udGVudHVwbG9hZHMyMDE4MTFhaXItam9yZGFuLTQtZG8tdGhlLXJpZ2h0LXRoaW5nLTMuanBn.imageset │ │ │ ├── d3AtY29udGVudHVwbG9hZHMyMDE4MTFhaXItam9yZGFuLTQtZG8tdGhlLXJpZ2h0LXRoaW5nLTMuanBn.jpg │ │ │ └── Contents.json │ │ ├── wp-contentuploads201903Air-Jordan-4-GS-Monsoon-Blue-BQ9043-400-Release-Date-Price.imageset │ │ │ ├── wp-contentuploads201903Air-Jordan-4-GS-Monsoon-Blue-BQ9043-400-Release-Date-Price.jpg │ │ │ └── Contents.json │ │ ├── wp-contentuploads201904Nike-Kyrie-5-Mamba-Mentality-AO2918-102-Release-Date-Price.imageset │ │ │ ├── wp-contentuploads201904Nike-Kyrie-5-Mamba-Mentality-AO2918-102-Release-Date-Price.jpg │ │ │ └── Contents.json │ │ ├── aimagest_prod_ssw_960,c_limit,f_autocys1ywaekpuvespws0x4lebron-16-remix-release-date.imageset │ │ │ ├── aimagest_prod_ssw_960,c_limit,f_autocys1ywaekpuvespws0x4lebron-16-remix-release-date.jpg │ │ │ └── Contents.json │ │ ├── d3AtY29udGVudHVwbG9hZHMyMDE4MDRhaXItam9yZGFuLTEwLW9ybGFuZG8tcmVsZWFzZS1pbmZvLmpwZw==.imageset │ │ │ ├── d3AtY29udGVudHVwbG9hZHMyMDE4MDRhaXItam9yZGFuLTEwLW9ybGFuZG8tcmVsZWFzZS1pbmZvLmpwZw==.jpg │ │ │ └── Contents.json │ │ ├── wp-contentuploads201903nike-air-fear-of-god-moccasin-black-at8086-002-release-date-1.imageset │ │ │ ├── wp-contentuploads201903nike-air-fear-of-god-moccasin-black-at8086-002-release-date-1.jpg │ │ │ └── Contents.json │ │ ├── aimagest_prod_ssw_960,c_limit,f_autoxrj4afk5rpddzxbbx6wclebron-16-martin-release-date.imageset │ │ │ ├── aimagest_prod_ssw_960,c_limit,f_autoxrj4afk5rpddzxbbx6wclebron-16-martin-release-date.jpg │ │ │ └── Contents.json │ │ ├── aimagest_prod_ssw_960,c_limit,f_autoegkplcxbgvbraxiwrkv4air-max2-light-atmos-release-date.imageset │ │ │ ├── aimagest_prod_ssw_960,c_limit,f_autoegkplcxbgvbraxiwrkv4air-max2-light-atmos-release-date.jpg │ │ │ └── Contents.json │ │ ├── aimagest_prod_ssw_960,c_limit,f_autoq8vrexcyxpmj80u4esgvlebron-16-air-trainer-release-date.imageset │ │ │ ├── aimagest_prod_ssw_960,c_limit,f_autoq8vrexcyxpmj80u4esgvlebron-16-air-trainer-release-date.jpg │ │ │ └── Contents.json │ │ ├── aimagest_prod_ssw_960,c_limit,f_autovziuczzeyzitgvjtar79air-max-1-on-air-tokyo-release-date.imageset │ │ │ ├── aimagest_prod_ssw_960,c_limit,f_autovziuczzeyzitgvjtar79air-max-1-on-air-tokyo-release-date.jpg │ │ │ └── Contents.json │ │ ├── aimagest_prod_ssw_960,c_limit,f_autovztgxht8el3fnj7oe6uzair-max-98-on-air-nyc-release-date.imageset │ │ │ ├── aimagest_prod_ssw_960,c_limit,f_autovztgxht8el3fnj7oe6uzair-max-98-on-air-nyc-release-date.jpg │ │ │ └── Contents.json │ │ ├── wp-contentuploads201903Air-Jordan-11-Low-Navy-Blue-Snakeskin-CD6846-102-Release-Date-Price.imageset │ │ │ ├── wp-contentuploads201903Air-Jordan-11-Low-Navy-Blue-Snakeskin-CD6846-102-Release-Date-Price.jpg │ │ │ └── Contents.json │ │ ├── aimagest_prod_ssw_960,c_limit,f_autokqoxst75honzfjrnqqyjair-max-97-on-air-seoul-release-date.imageset │ │ │ ├── aimagest_prod_ssw_960,c_limit,f_autokqoxst75honzfjrnqqyjair-max-97-on-air-seoul-release-date.jpg │ │ │ └── Contents.json │ │ ├── wp-contentuploads201901nike-air-fear-of-god-moccasin-pure-platinum-AT8086-001-release-date-1.imageset │ │ │ ├── wp-contentuploads201901nike-air-fear-of-god-moccasin-pure-platinum-AT8086-001-release-date-1.jpg │ │ │ └── Contents.json │ │ ├── aimagest_PDP_864_v1f_auto,b_rgb:f5f5f5ecliaenqoa7rgkgvp2xyair-jordan-1-low-mens-shoe-z3Tl2VeJ.imageset │ │ │ ├── aimagest_PDP_864_v1f_auto,b_rgb:f5f5f5ecliaenqoa7rgkgvp2xyair-jordan-1-low-mens-shoe-z3Tl2VeJ.jpg │ │ │ └── Contents.json │ │ ├── aimagest_prod_ssw_960,c_limit,f_autopselea04ks9hnkcbsahdair-max-97-on-air-london-release-date.imageset │ │ │ ├── aimagest_prod_ssw_960,c_limit,f_autopselea04ks9hnkcbsahdair-max-97-on-air-london-release-date.jpg │ │ │ └── Contents.json │ │ ├── aimagest_prod_ssw_960,c_limit,f_autovxm7ancrgvsdat5i7o0unike-pg-3-mamba-mentality-release-date.imageset │ │ │ ├── aimagest_prod_ssw_960,c_limit,f_autovxm7ancrgvsdat5i7o0unike-pg-3-mamba-mentality-release-date.jpg │ │ │ └── Contents.json │ │ ├── aimagest_prod_ssw_960,c_limit,f_autobyweh2fwj8cxlvkcjyjbadapt-bb-future-of-the-game-release-date.imageset │ │ │ ├── aimagest_prod_ssw_960,c_limit,f_autobyweh2fwj8cxlvkcjyjbadapt-bb-future-of-the-game-release-date.jpg │ │ │ └── Contents.json │ │ ├── aimagest_prod_ssw_960,c_limit,f_autotunyzqjx91k3yk55mggeair-max2-light-purple-berry-release-date.imageset │ │ │ ├── aimagest_prod_ssw_960,c_limit,f_autotunyzqjx91k3yk55mggeair-max2-light-purple-berry-release-date.jpg │ │ │ └── Contents.json │ │ ├── aimagest_prod_ssw_960,c_limit,f_autourcu79fr3do9u0a4soicair-max-97-on-air-shanghai-release-date.imageset │ │ │ ├── aimagest_prod_ssw_960,c_limit,f_autourcu79fr3do9u0a4soicair-max-97-on-air-shanghai-release-date.jpg │ │ │ └── Contents.json │ │ ├── aimagest_prod_ssw_960,c_limit,f_autoepba4fhpfb9ajdmpmea2air-foamposite-hyper-crimson-release-date.imageset │ │ │ ├── aimagest_prod_ssw_960,c_limit,f_autoepba4fhpfb9ajdmpmea2air-foamposite-hyper-crimson-release-date.jpg │ │ │ └── Contents.json │ │ ├── solelinksstoragereleaseImages3666AQ1090-200_sivasdescalzo-nike-NIKE-REACT-ELEMENT-87-AQ1090-200-1.imageset │ │ │ ├── solelinksstoragereleaseImages3666AQ1090-200_sivasdescalzo-nike-NIKE-REACT-ELEMENT-87-AQ1090-200-1.jpg │ │ │ └── Contents.json │ │ ├── solelinksstoragereleaseImages3667AQ1090-300_sivasdescalzo-nike-NIKE-REACT-ELEMENT-87-AQ1090-300-1.imageset │ │ │ ├── solelinksstoragereleaseImages3667AQ1090-300_sivasdescalzo-nike-NIKE-REACT-ELEMENT-87-AQ1090-300-1.jpg │ │ │ └── Contents.json │ │ ├── aimagest_prod_lsw_1920,c_limit,f_autodil46qlauylplvdx6dudair-foamposite-hyper-crimson-release-date.imageset │ │ │ ├── aimagest_prod_lsw_1920,c_limit,f_autodil46qlauylplvdx6dudair-foamposite-hyper-crimson-release-date.jpg │ │ │ └── Contents.json │ │ ├── aimagest_prod_lsw_1920,c_limit,f_autovkzcokvv4fh2jjk71djdair-foamposite-hyper-crimson-release-date.imageset │ │ │ ├── aimagest_prod_lsw_1920,c_limit,f_autovkzcokvv4fh2jjk71djdair-foamposite-hyper-crimson-release-date.jpg │ │ │ └── Contents.json │ │ ├── aimagest_prod_lsw_1920,c_limit,f_autoy0udif4vqu87bklmxs9uair-foamposite-hyper-crimson-release-date.imageset │ │ │ ├── aimagest_prod_lsw_1920,c_limit,f_autoy0udif4vqu87bklmxs9uair-foamposite-hyper-crimson-release-date.jpg │ │ │ └── Contents.json │ │ ├── aimagest_prod_ssw_960,c_limit,f_autoxcsral03nckwhqi3vshbair-vapormax-plus-on-air-paris-release-date.imageset │ │ │ ├── aimagest_prod_ssw_960,c_limit,f_autoxcsral03nckwhqi3vshbair-vapormax-plus-on-air-paris-release-date.jpg │ │ │ └── Contents.json │ │ ├── aimagest_prod_ssw_960,c_limit,f_autocr2smyshlm9vamsquxamnike-blazer-mid-77-black-canvas-release-date.imageset │ │ │ ├── aimagest_prod_ssw_960,c_limit,f_autocr2smyshlm9vamsquxamnike-blazer-mid-77-black-canvas-release-date.jpg │ │ │ └── Contents.json │ │ ├── aimagest_prod_ssw_960,c_limit,f_autot8cz8v6lx119os3pn94onike-air-trainer-3-medicine-ball-release-date.imageset │ │ │ ├── aimagest_prod_ssw_960,c_limit,f_autot8cz8v6lx119os3pn94onike-air-trainer-3-medicine-ball-release-date.jpg │ │ │ └── Contents.json │ │ ├── aimagest_prod_ssw_960,c_limit,f_autoyqhwscvw99o0a1w6pmwiwomens-air-jordan-12-midnight-black-release-date.imageset │ │ │ ├── aimagest_prod_ssw_960,c_limit,f_autoyqhwscvw99o0a1w6pmwiwomens-air-jordan-12-midnight-black-release-date.jpg │ │ │ └── Contents.json │ │ ├── d3AtY29udGVudHVwbG9hZHMyMDE4MDZOaWtlLUt5cmllLTQtVHJpcGxlLUJsYWNrLTk0MzgwNy0wMDgtUmVsZWFzZS1EYXRlLmpwZw==.imageset │ │ │ ├── d3AtY29udGVudHVwbG9hZHMyMDE4MDZOaWtlLUt5cmllLTQtVHJpcGxlLUJsYWNrLTk0MzgwNy0wMDgtUmVsZWFzZS1EYXRlLmpwZw==.jpg │ │ │ └── Contents.json │ │ ├── aimagest_prod_ssw_960,c_limit,f_autojejlqawpimm6qoanfwzcnike-blazer-mid-77-vintage-pink-foam-release-date.imageset │ │ │ ├── aimagest_prod_ssw_960,c_limit,f_autojejlqawpimm6qoanfwzcnike-blazer-mid-77-vintage-pink-foam-release-date.jpg │ │ │ └── Contents.json │ │ ├── aimagest_prod_ssw_960,c_limit,f_autovp6fcofbqm8skzhjuldmnike-blazer-mid-77-vintage-sail-white-release-date.imageset │ │ │ ├── aimagest_prod_ssw_960,c_limit,f_autovp6fcofbqm8skzhjuldmnike-blazer-mid-77-vintage-sail-white-release-date.jpg │ │ │ └── Contents.json │ │ ├── bWVkaWFjYXRhbG9ncHJvZHVjdGNhY2hlMWJhc2UxMDAweDYwMDlkZjc4ZWFiMzM1MjVkMDhkNmU1ZmI4ZDI3MTM2ZTk1RUVFRTc1NzlfMS5wbmc=.imageset │ │ │ ├── bWVkaWFjYXRhbG9ncHJvZHVjdGNhY2hlMWJhc2UxMDAweDYwMDlkZjc4ZWFiMzM1MjVkMDhkNmU1ZmI4ZDI3MTM2ZTk1RUVFRTc1NzlfMS5wbmc=.png │ │ │ └── Contents.json │ │ ├── bWVkaWFjYXRhbG9ncHJvZHVjdGNhY2hlMWJhc2UxMDAweDYwMDlkZjc4ZWFiMzM1MjVkMDhkNmU1ZmI4ZDI3MTM2ZTk1RUVFRTc1ODNfMS5wbmc=.imageset │ │ │ ├── bWVkaWFjYXRhbG9ncHJvZHVjdGNhY2hlMWJhc2UxMDAweDYwMDlkZjc4ZWFiMzM1MjVkMDhkNmU1ZmI4ZDI3MTM2ZTk1RUVFRTc1ODNfMS5wbmc=.png │ │ │ └── Contents.json │ │ ├── aimagest_prod_ssw_960,c_limit,f_autoeyeg1kej2njudfya2fhqnike-womens-air-max-95-plant-color-collection-release-date.imageset │ │ │ ├── aimagest_prod_ssw_960,c_limit,f_autoeyeg1kej2njudfya2fhqnike-womens-air-max-95-plant-color-collection-release-date.jpg │ │ │ └── Contents.json │ │ ├── bWVkaWFjYXRhbG9ncHJvZHVjdGNhY2hlMWJhc2UxMDAweDYwMDlkZjc4ZWFiMzM1MjVkMDhkNmU1ZmI4ZDI3MTM2ZTk1ZWVlZTc1ODFfMV8xLnBuZw==.imageset │ │ │ ├── bWVkaWFjYXRhbG9ncHJvZHVjdGNhY2hlMWJhc2UxMDAweDYwMDlkZjc4ZWFiMzM1MjVkMDhkNmU1ZmI4ZDI3MTM2ZTk1ZWVlZTc1ODFfMV8xLnBuZw==.png │ │ │ └── Contents.json │ │ ├── d3AtY29udGVudHVwbG9hZHMyMDE4MDZKb3JkYW4tTGVnYWN5LTMxMi1TdG9ybS1CbHVlLVJvYXlsLUFRNDE2MC0xMDQtUmVsZWFzZS1EYXRlLmpwZw==.imageset │ │ │ ├── d3AtY29udGVudHVwbG9hZHMyMDE4MDZKb3JkYW4tTGVnYWN5LTMxMi1TdG9ybS1CbHVlLVJvYXlsLUFRNDE2MC0xMDQtUmVsZWFzZS1EYXRlLmpwZw==.jpg │ │ │ └── Contents.json │ │ ├── d3AtY29udGVudHVwbG9hZHMyMDE4MTFOaWtlLUFpci1Gb3JjZS0yNzAtVXRpbGl0eS1CbGFjay1Wb2x0LUFRMDU3Mi0wMDEtUmVsZWFzZS1EYXRlLmpwZw==.imageset │ │ │ ├── d3AtY29udGVudHVwbG9hZHMyMDE4MTFOaWtlLUFpci1Gb3JjZS0yNzAtVXRpbGl0eS1CbGFjay1Wb2x0LUFRMDU3Mi0wMDEtUmVsZWFzZS1EYXRlLmpwZw==.jpg │ │ │ └── Contents.json │ │ ├── YWltYWdlc2ZfYXV0byxiX3JnYjpmNWY1ZjUsd180NDBuenlibnRkZDNhY3B3YW1xYXFycWNvcnRlei1iYXNpYy1sZWF0aGVyLW1lbnMtc2hvZS1SUFBDRzUuanBn.imageset │ │ │ ├── YWltYWdlc2ZfYXV0byxiX3JnYjpmNWY1ZjUsd180NDBuenlibnRkZDNhY3B3YW1xYXFycWNvcnRlei1iYXNpYy1sZWF0aGVyLW1lbnMtc2hvZS1SUFBDRzUuanBn.jpg │ │ │ └── Contents.json │ │ ├── d3d3LnNuZWFrZXJlbGl0ZS5jb213cC1jb250ZW50dXBsb2FkczIwMTgwNmFkaWRhcy1ZZWV6eS1Cb29zdC0zNTAtVjItQnV0dGVyLVJlbGVhc2UtRGF0ZS1GMzY5ODAuanBn.imageset │ │ │ ├── d3d3LnNuZWFrZXJlbGl0ZS5jb213cC1jb250ZW50dXBsb2FkczIwMTgwNmFkaWRhcy1ZZWV6eS1Cb29zdC0zNTAtVjItQnV0dGVyLVJlbGVhc2UtRGF0ZS1GMzY5ODAuanBn.jpg │ │ │ └── Contents.json │ │ ├── YWltYWdlc2ZfYXV0byxiX3JnYjpmNWY1ZjUsd180NDBmc29sM2I3aGUxcnJkYmpsbTV4MGFpci1mb290c2NhcGUtbWlkLXV0aWxpdHktZG0tbWVucy1zaG9lLXM3WGdTNC5qcGc=.imageset │ │ │ ├── YWltYWdlc2ZfYXV0byxiX3JnYjpmNWY1ZjUsd180NDBmc29sM2I3aGUxcnJkYmpsbTV4MGFpci1mb290c2NhcGUtbWlkLXV0aWxpdHktZG0tbWVucy1zaG9lLXM3WGdTNC5qcGc=.jpg │ │ │ └── Contents.json │ │ ├── YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvazF1OHJnMmZuemZwMjJqZTdwbnVuaWtlLWFpci1mb3JjZS0xLXByZW1pdW0tbjctcmVsZWFzZS1kYXRlLmpwZw==.imageset │ │ │ ├── YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvazF1OHJnMmZuemZwMjJqZTdwbnVuaWtlLWFpci1mb3JjZS0xLXByZW1pdW0tbjctcmVsZWFzZS1kYXRlLmpwZw==.jpg │ │ │ └── Contents.json │ │ ├── YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvbnlieW95ampmYzhrbzVnZ3dianJ3b21lbnMtYWlyLWpvcmRhbi0zLWJvcmRlYXV4LXJlbGVhc2UtZGF0ZS5qcGc=.imageset │ │ │ ├── YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvbnlieW95ampmYzhrbzVnZ3dianJ3b21lbnMtYWlyLWpvcmRhbi0zLWJvcmRlYXV4LXJlbGVhc2UtZGF0ZS5qcGc=.jpg │ │ │ └── Contents.json │ │ ├── YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvZjViM2E1amNxaWFycm5qZzR0aWJuaWtlLWFpci1mb3JjZS0xLXByZW1pdW0tanVzdC1kby1pdC1yZWxlYXNlLWRhdGUuanBn.imageset │ │ │ ├── YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvZjViM2E1amNxaWFycm5qZzR0aWJuaWtlLWFpci1mb3JjZS0xLXByZW1pdW0tanVzdC1kby1pdC1yZWxlYXNlLWRhdGUuanBn.jpg │ │ │ └── Contents.json │ │ ├── YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvcDl0cnV4eHB2M3Bkb2U3M3hobG9haXItam9yZGFuLTYtcmV0cm8tdGlua2VyLWluZnJhcmVkLXJlbGVhc2UtZGF0ZS5qcGc=.imageset │ │ │ ├── YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvcDl0cnV4eHB2M3Bkb2U3M3hobG9haXItam9yZGFuLTYtcmV0cm8tdGlua2VyLWluZnJhcmVkLXJlbGVhc2UtZGF0ZS5qcGc=.jpg │ │ │ └── Contents.json │ │ ├── YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRveTU0ejh3bnc0ZWVyMXR4MWFrdmluaWtlLWFpci1tYXgtOTctYmxhY2stbWV0YWxsaWMtZ29sZC1yZWxlYXNlLWRhdGUuanBn.imageset │ │ │ ├── YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRveTU0ejh3bnc0ZWVyMXR4MWFrdmluaWtlLWFpci1tYXgtOTctYmxhY2stbWV0YWxsaWMtZ29sZC1yZWxlYXNlLWRhdGUuanBn.jpg │ │ │ └── Contents.json │ │ ├── YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvYXd4YXh5N2l0cDA0eWx5ZGlwY2FuaWtlLWFpci1tYXgtOTUtbnJnLWJsYWNrLWFudGhyYWNpdGUtcmVsZWFzZS1kYXRlLmpwZw==.imageset │ │ │ ├── Contents.json │ │ │ └── YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvYXd4YXh5N2l0cDA0eWx5ZGlwY2FuaWtlLWFpci1tYXgtOTUtbnJnLWJsYWNrLWFudGhyYWNpdGUtcmVsZWFzZS1kYXRlLmpwZw==.jpg │ │ ├── YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvd29rOG0zcHgwMTF2ZWptNzJzeWRuaWtlLWFpci1tYXgtOTctbWV0YWxsaWMtc2lsdmVyLWJsYWNrLXJlbGVhc2UtZGF0ZS5qcGc=.imageset │ │ │ ├── Contents.json │ │ │ └── YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvd29rOG0zcHgwMTF2ZWptNzJzeWRuaWtlLWFpci1tYXgtOTctbWV0YWxsaWMtc2lsdmVyLWJsYWNrLXJlbGVhc2UtZGF0ZS5qcGc=.jpg │ │ ├── YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvd2oxYXJ3Z21xazB4MHJqbm54aDduaWtlLWFpci1tYXgtOTctbWV0YWxsaWMtc2lsdmVyLXdoaXRlLXJlbGVhc2UtZGF0ZS5qcGc=.imageset │ │ │ ├── Contents.json │ │ │ └── YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvd2oxYXJ3Z21xazB4MHJqbm54aDduaWtlLWFpci1tYXgtOTctbWV0YWxsaWMtc2lsdmVyLXdoaXRlLXJlbGVhc2UtZGF0ZS5qcGc=.jpg │ │ ├── YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvanF3eW1laGJ5c21ueG1hd3JyNmJ3b21lbnMtYWlyLWZvcmNlLTEtc2FnZS1oaWdoLXdoaXRlLWJsYWNrLXJlbGVhc2UtZGF0ZS5qcGc=.imageset │ │ │ ├── Contents.json │ │ │ └── YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvanF3eW1laGJ5c21ueG1hd3JyNmJ3b21lbnMtYWlyLWZvcmNlLTEtc2FnZS1oaWdoLXdoaXRlLWJsYWNrLXJlbGVhc2UtZGF0ZS5qcGc=.jpg │ │ ├── YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvbWJpaDc5Z3htbG1veTFhbmx5cHZ3b21lbnMtYWYxLWplc3Rlci1oaS14eC1ibGFjay1kdXN0eS1wZWFjaC1yZWxlYXNlLWRhdGUuanBn.imageset │ │ │ ├── Contents.json │ │ │ └── YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvbWJpaDc5Z3htbG1veTFhbmx5cHZ3b21lbnMtYWYxLWplc3Rlci1oaS14eC1ibGFjay1kdXN0eS1wZWFjaC1yZWxlYXNlLWRhdGUuanBn.jpg │ │ ├── YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvc2Jsc3F6aWthcHFnemk3OXl5cXNuaWtlLWFpci1tYXgtOTctbnJnLXRlYW0tcmVkLW1pZG5pZ2h0LXNwcnVjZS1yZWxlYXNlLWRhdGUuanBn.imageset │ │ │ ├── Contents.json │ │ │ └── YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvc2Jsc3F6aWthcHFnemk3OXl5cXNuaWtlLWFpci1tYXgtOTctbnJnLXRlYW0tcmVkLW1pZG5pZ2h0LXNwcnVjZS1yZWxlYXNlLWRhdGUuanBn.jpg │ │ ├── YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvcXh1MDBpaHh5dDVyZjBzZ2ZyYnVuaWtlLWFpci1tYXgtOTUtcHJlbWl1bS12b2x0LXZvbHQtZ2xvdy1ibGFjay1yZWxlYXNlLWRhdGUuanBn.imageset │ │ │ ├── Contents.json │ │ │ └── YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvcXh1MDBpaHh5dDVyZjBzZ2ZyYnVuaWtlLWFpci1tYXgtOTUtcHJlbWl1bS12b2x0LXZvbHQtZ2xvdy1ibGFjay1yZWxlYXNlLWRhdGUuanBn.jpg │ │ ├── YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvdTVkOGNscms4dGN3anR1aXlsYnZuaWtlLXdvbWVucy1haXItZm9yY2UtMS1yZWJlbC14eC1wcmFsaW5lLXdoaXRlLXJlbGVhc2UtZGF0ZS5qcGc=.imageset │ │ │ ├── Contents.json │ │ │ └── YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvdTVkOGNscms4dGN3anR1aXlsYnZuaWtlLXdvbWVucy1haXItZm9yY2UtMS1yZWJlbC14eC1wcmFsaW5lLXdoaXRlLXJlbGVhc2UtZGF0ZS5qcGc=.jpg │ │ ├── YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvdWNycWRwMXdiYnh2cWp5MDdmcmV3b21lbnMtYWYxLWplc3Rlci1oaS14eC12aW9sZXQtYXNoLWJsdWUtZm9yY2UtcmVsZWFzZS1kYXRlLmpwZw==.imageset │ │ │ ├── Contents.json │ │ │ └── YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvdWNycWRwMXdiYnh2cWp5MDdmcmV3b21lbnMtYWYxLWplc3Rlci1oaS14eC12aW9sZXQtYXNoLWJsdWUtZm9yY2UtcmVsZWFzZS1kYXRlLmpwZw==.jpg │ │ ├── YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRveXFhY3F2cWNqaHFwYmduZ3VheWVhaXItam9yZGFuLTEyLWludGVybmF0aW9uYWwtZmxpZ2h0LWNvbGxlZ2UtbmF2eS1yZWxlYXNlLWRhdGUuanBn.imageset │ │ │ ├── Contents.json │ │ │ └── YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRveXFhY3F2cWNqaHFwYmduZ3VheWVhaXItam9yZGFuLTEyLWludGVybmF0aW9uYWwtZmxpZ2h0LWNvbGxlZ2UtbmF2eS1yZWxlYXNlLWRhdGUuanBn.jpg │ │ ├── YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvcThpMXJvZGd4OWNjaXI1Ymdzc2VuaWtlLWFpci1mb3JjZS0xLWx2OC11dGlsaXR5LXZvbHQtd29sZi1ncmV5LXdoaXRlLXJlbGVhc2UtZGF0ZS5qcGc=.imageset │ │ │ ├── Contents.json │ │ │ └── YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvcThpMXJvZGd4OWNjaXI1Ymdzc2VuaWtlLWFpci1mb3JjZS0xLWx2OC11dGlsaXR5LXZvbHQtd29sZi1ncmV5LXdoaXRlLXJlbGVhc2UtZGF0ZS5qcGc=.jpg │ │ ├── YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvdmd4NXR6cmd2YTBpcmxzYnJjcGluaWtlLXNiLWxvdy1wcm8tZGlhbW9uZC1ibGFjay10cm9waWNhbC10d2lzdC1jaHJvbWUtcmVsZWFzZS1kYXRlLmpwZw==.imageset │ │ │ ├── Contents.json │ │ │ └── YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvdmd4NXR6cmd2YTBpcmxzYnJjcGluaWtlLXNiLWxvdy1wcm8tZGlhbW9uZC1ibGFjay10cm9waWNhbC10d2lzdC1jaHJvbWUtcmVsZWFzZS1kYXRlLmpwZw==.jpg │ │ ├── YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvd3drY29xcHY5dnh5aDRwZ3dkZ25uaWtlLWFpci1mb3JjZS0xLXByZW1pdW0tanVzdC1kby1pdC1jb2xsZWN0aW9uLXRvdGFsLW9yYW5nZS1yZWxlYXNlLWRhdGUuanBn.imageset │ │ │ ├── Contents.json │ │ │ └── YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvd3drY29xcHY5dnh5aDRwZ3dkZ25uaWtlLWFpci1mb3JjZS0xLXByZW1pdW0tanVzdC1kby1pdC1jb2xsZWN0aW9uLXRvdGFsLW9yYW5nZS1yZWxlYXNlLWRhdGUuanBn.jpg │ │ └── compleximagesfl_lossy,q_autoc_crop,h_502,w_856,x_0,y_183c_scale,w_690,dpr_2.0v1ctxstklbxucwkotuhh72air-jordan-1-shattered-backboard-3-0-black-pale-vanilla-starfish-555088-028-rendering.imageset │ │ │ ├── Contents.json │ │ │ └── compleximagesfl_lossy,q_autoc_crop,h_502,w_856,x_0,y_183c_scale,w_690,dpr_2.0v1ctxstklbxucwkotuhh72air-jordan-1-shattered-backboard-3-0-black-pale-vanilla-starfish-555088-028-rendering.jpg │ ├── banner │ │ ├── Contents.json │ │ ├── banner1.imageset │ │ │ ├── banner1.jpg │ │ │ └── Contents.json │ │ ├── banner2.imageset │ │ │ ├── banner2.jpg │ │ │ └── Contents.json │ │ ├── banner3.imageset │ │ │ ├── banner3.jpg │ │ │ └── Contents.json │ │ ├── banner4.imageset │ │ │ ├── banner4.jpg │ │ │ └── Contents.json │ │ ├── banner5.imageset │ │ │ ├── banner5.jpg │ │ │ └── Contents.json │ │ └── banner6.imageset │ │ │ ├── banner6.jpg │ │ │ └── Contents.json │ ├── loading │ │ ├── Contents.json │ │ ├── frame-0.imageset │ │ │ ├── frame-0.png │ │ │ └── Contents.json │ │ ├── frame-1.imageset │ │ │ ├── frame-1.png │ │ │ └── Contents.json │ │ ├── frame-2.imageset │ │ │ ├── frame-2.png │ │ │ └── Contents.json │ │ ├── frame-3.imageset │ │ │ ├── frame-3.png │ │ │ └── Contents.json │ │ ├── frame-4.imageset │ │ │ ├── frame-4.png │ │ │ └── Contents.json │ │ ├── frame-5.imageset │ │ │ ├── frame-5.png │ │ │ └── Contents.json │ │ ├── frame-6.imageset │ │ │ ├── frame-6.png │ │ │ └── Contents.json │ │ ├── frame-7.imageset │ │ │ ├── frame-7.png │ │ │ └── Contents.json │ │ ├── frame-8.imageset │ │ │ ├── frame-8.png │ │ │ └── Contents.json │ │ ├── frame-9.imageset │ │ │ ├── frame-9.png │ │ │ └── Contents.json │ │ ├── frame-10.imageset │ │ │ ├── frame-10.png │ │ │ └── Contents.json │ │ ├── frame-11.imageset │ │ │ ├── frame-11.png │ │ │ └── Contents.json │ │ ├── frame-12.imageset │ │ │ ├── frame-12.png │ │ │ └── Contents.json │ │ ├── frame-13.imageset │ │ │ ├── frame-13.png │ │ │ └── Contents.json │ │ ├── frame-14.imageset │ │ │ ├── frame-14.png │ │ │ └── Contents.json │ │ ├── frame-15.imageset │ │ │ ├── frame-15.png │ │ │ └── Contents.json │ │ ├── frame-16.imageset │ │ │ ├── frame-16.png │ │ │ └── Contents.json │ │ ├── frame-17.imageset │ │ │ ├── frame-17.png │ │ │ └── Contents.json │ │ ├── frame-18.imageset │ │ │ ├── frame-18.png │ │ │ └── Contents.json │ │ ├── frame-19.imageset │ │ │ ├── frame-19.png │ │ │ └── Contents.json │ │ ├── frame-20.imageset │ │ │ ├── frame-20.png │ │ │ └── Contents.json │ │ ├── frame-21.imageset │ │ │ ├── frame-21.png │ │ │ └── Contents.json │ │ ├── frame-22.imageset │ │ │ ├── frame-22.png │ │ │ └── Contents.json │ │ ├── frame-23.imageset │ │ │ ├── frame-23.png │ │ │ └── Contents.json │ │ ├── frame-24.imageset │ │ │ ├── frame-24.png │ │ │ └── Contents.json │ │ ├── frame-25.imageset │ │ │ ├── frame-25.png │ │ │ └── Contents.json │ │ ├── frame-26.imageset │ │ │ ├── frame-26.png │ │ │ └── Contents.json │ │ ├── frame-27.imageset │ │ │ ├── frame-27.png │ │ │ └── Contents.json │ │ ├── frame-28.imageset │ │ │ ├── frame-28.png │ │ │ └── Contents.json │ │ └── frame-29.imageset │ │ │ ├── frame-29.png │ │ │ └── Contents.json │ └── AppIcon.appiconset │ │ ├── Icon-20.png │ │ ├── Icon-29.png │ │ ├── Icon-40.png │ │ ├── Icon-76.png │ │ ├── Icon-20@2x.png │ │ ├── Icon-20@3x.png │ │ ├── Icon-29@2x.png │ │ ├── Icon-29@3x.png │ │ ├── Icon-40@2x.png │ │ ├── Icon-40@3x.png │ │ ├── Icon-512@2x.png │ │ ├── Icon-60@2x.png │ │ ├── Icon-60@3x.png │ │ ├── Icon-76@2x.png │ │ ├── Icon-20@2x-1.png │ │ ├── Icon-29@2x-1.png │ │ ├── Icon-40@2x-1.png │ │ └── Icon-83.5@2x.png ├── Other │ ├── Util │ │ ├── NameProvidable.swift │ │ ├── ProjectNameHelper.swift │ │ ├── ScrollViewCalculator.swift │ │ └── NotificationUtility.swift │ ├── Extension │ │ ├── String+extensions.swift │ │ ├── UIColor+extensions.swift │ │ ├── UIStoryboard+Extensions.swift │ │ ├── UICollectionView+Extensions.swift │ │ └── UITableView+Extensions.swift │ └── Constants │ │ └── ThemeColor.swift ├── DomainLayer │ ├── DataModel │ │ ├── BannerModel.swift │ │ └── ShoesModel.swift │ ├── ShoesRepositorySpec.swift │ ├── BannerRepositorySpec.swift │ └── UseCases │ │ ├── FetchDataUseCaseSpec.swift │ │ ├── FetchShoesUseCase.swift │ │ └── FetchBannerUseCase.swift ├── DataLayer │ ├── DataModel │ │ ├── RemoteBannerModel.swift │ │ └── RemoteBaseModel.swift │ └── Repositories │ │ ├── Remote │ │ ├── NetworkFetchable.swift │ │ ├── RemoteBannerRepository.swift │ │ ├── RemoteShoesRepository.swift │ │ ├── BannerFetcher.swift │ │ └── ShoesListFetcher.swift │ │ ├── Cache │ │ └── CacheShoesRepository.swift │ │ └── Local │ │ └── LocalShoesRepository.swift ├── ViewLayer │ ├── Scenes │ │ ├── SetReuseViewModelable.swift │ │ ├── ShoesList │ │ │ ├── Banner │ │ │ │ ├── ShoesListBannerCollectionViewCell.swift │ │ │ │ ├── ShoesListBannerRouter.swift │ │ │ │ ├── ShoesListBannerBuilder.swift │ │ │ │ └── ShoesListBannerPresenter.swift │ │ │ ├── ShoesListRouter.swift │ │ │ ├── ShoesListTableViewCell.swift │ │ │ └── ShoesListViewBuilder.swift │ │ ├── ViewBuilderSpec.swift │ │ └── ShoesDetail │ │ │ ├── ShoesDetailViewBuilder.swift │ │ │ ├── ShoesDetailDescriptionTableViewCell.swift │ │ │ ├── ShoesDetailInfoTableViewCell.swift │ │ │ └── ShoesDetailPresenter.swift │ ├── BaseClass │ │ ├── BaseViewController.swift │ │ └── BaseNavigationController.swift │ └── CustomView │ │ ├── Loading │ │ ├── LoadingImageView.swift │ │ └── LoadingViewManager.swift │ │ └── FailHeaderView.swift ├── AppDelegate.swift ├── SupportFiles │ └── Dummy │ │ └── banner_remote_source.json └── Info.plist ├── MVP-Sample.xcodeproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── MVP-SampleTests ├── Helper │ ├── Model │ │ ├── RemoteBannerModel.swift │ │ ├── BannerModel.swift │ │ └── ShoesModel.swift │ └── Repository │ │ ├── MockFetchShoesRepository.swift │ │ └── MockRemoteBannerRepository.swift ├── Info.plist ├── MVP_SampleTests.swift └── DomainLayer │ ├── FetchShoesUseCaseTests.swift │ └── FetchBannerUseCaseTests.swift ├── LICENSE ├── .gitignore └── README.md /architecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/architecture.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/banner/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/AppIcon.appiconset/Icon-20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/AppIcon.appiconset/Icon-20.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/AppIcon.appiconset/Icon-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/AppIcon.appiconset/Icon-29.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/AppIcon.appiconset/Icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/AppIcon.appiconset/Icon-40.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/AppIcon.appiconset/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/AppIcon.appiconset/Icon-76.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/AppIcon.appiconset/Icon-20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/AppIcon.appiconset/Icon-20@2x.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/AppIcon.appiconset/Icon-20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/AppIcon.appiconset/Icon-20@3x.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/AppIcon.appiconset/Icon-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/AppIcon.appiconset/Icon-29@2x.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/AppIcon.appiconset/Icon-29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/AppIcon.appiconset/Icon-29@3x.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/AppIcon.appiconset/Icon-512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/AppIcon.appiconset/Icon-512@2x.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/12732.imageset/12732@3x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/12732.imageset/12732@3x.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/12746.imageset/12746@3x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/12746.imageset/12746@3x.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/12837.imageset/12837@3x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/12837.imageset/12837@3x.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/12841.imageset/12841@3x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/12841.imageset/12841@3x.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/9Osfhjm.imageset/9Osfhjm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/9Osfhjm.imageset/9Osfhjm.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/JwKauWO.imageset/JwKauWO.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/JwKauWO.imageset/JwKauWO.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/N6OObvC.imageset/N6OObvC.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/N6OObvC.imageset/N6OObvC.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/QRdx9Il.imageset/QRdx9Il.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/QRdx9Il.imageset/QRdx9Il.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/TNOsGqn.imageset/TNOsGqn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/TNOsGqn.imageset/TNOsGqn.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/cQnmyKQ.imageset/cQnmyKQ.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/cQnmyKQ.imageset/cQnmyKQ.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/mm6pknp.imageset/mm6pknp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/mm6pknp.imageset/mm6pknp.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/o0l47Ca.imageset/o0l47Ca.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/o0l47Ca.imageset/o0l47Ca.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/p4Ud82Q.imageset/p4Ud82Q.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/p4Ud82Q.imageset/p4Ud82Q.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/AppIcon.appiconset/Icon-20@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/AppIcon.appiconset/Icon-20@2x-1.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/AppIcon.appiconset/Icon-29@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/AppIcon.appiconset/Icon-29@2x-1.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/AppIcon.appiconset/Icon-40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/AppIcon.appiconset/Icon-40@2x-1.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/banner/banner1.imageset/banner1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/banner/banner1.imageset/banner1.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/banner/banner2.imageset/banner2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/banner/banner2.imageset/banner2.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/banner/banner3.imageset/banner3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/banner/banner3.imageset/banner3.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/banner/banner4.imageset/banner4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/banner/banner4.imageset/banner4.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/banner/banner5.imageset/banner5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/banner/banner5.imageset/banner5.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/banner/banner6.imageset/banner6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/banner/banner6.imageset/banner6.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-0.imageset/frame-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/loading/frame-0.imageset/frame-0.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-1.imageset/frame-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/loading/frame-1.imageset/frame-1.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-2.imageset/frame-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/loading/frame-2.imageset/frame-2.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-3.imageset/frame-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/loading/frame-3.imageset/frame-3.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-4.imageset/frame-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/loading/frame-4.imageset/frame-4.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-5.imageset/frame-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/loading/frame-5.imageset/frame-5.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-6.imageset/frame-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/loading/frame-6.imageset/frame-6.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-7.imageset/frame-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/loading/frame-7.imageset/frame-7.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-8.imageset/frame-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/loading/frame-8.imageset/frame-8.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-9.imageset/frame-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/loading/frame-9.imageset/frame-9.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-10.imageset/frame-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/loading/frame-10.imageset/frame-10.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-11.imageset/frame-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/loading/frame-11.imageset/frame-11.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-12.imageset/frame-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/loading/frame-12.imageset/frame-12.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-13.imageset/frame-13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/loading/frame-13.imageset/frame-13.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-14.imageset/frame-14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/loading/frame-14.imageset/frame-14.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-15.imageset/frame-15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/loading/frame-15.imageset/frame-15.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-16.imageset/frame-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/loading/frame-16.imageset/frame-16.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-17.imageset/frame-17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/loading/frame-17.imageset/frame-17.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-18.imageset/frame-18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/loading/frame-18.imageset/frame-18.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-19.imageset/frame-19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/loading/frame-19.imageset/frame-19.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-20.imageset/frame-20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/loading/frame-20.imageset/frame-20.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-21.imageset/frame-21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/loading/frame-21.imageset/frame-21.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-22.imageset/frame-22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/loading/frame-22.imageset/frame-22.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-23.imageset/frame-23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/loading/frame-23.imageset/frame-23.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-24.imageset/frame-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/loading/frame-24.imageset/frame-24.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-25.imageset/frame-25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/loading/frame-25.imageset/frame-25.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-26.imageset/frame-26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/loading/frame-26.imageset/frame-26.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-27.imageset/frame-27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/loading/frame-27.imageset/frame-27.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-28.imageset/frame-28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/loading/frame-28.imageset/frame-28.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-29.imageset/frame-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/loading/frame-29.imageset/frame-29.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/M29vcEQ0Ry5qcGc=.imageset/M29vcEQ0Ry5qcGc=.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/M29vcEQ0Ry5qcGc=.imageset/M29vcEQ0Ry5qcGc=.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/MTE1MDZAM3guanBn.imageset/MTE1MDZAM3guanBn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/MTE1MDZAM3guanBn.imageset/MTE1MDZAM3guanBn.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/MTIxNjhAM3guanBn.imageset/MTIxNjhAM3guanBn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/MTIxNjhAM3guanBn.imageset/MTIxNjhAM3guanBn.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/RDdGVGV6eS5qcGc=.imageset/RDdGVGV6eS5qcGc=.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/RDdGVGV6eS5qcGc=.imageset/RDdGVGV6eS5qcGc=.jpg -------------------------------------------------------------------------------- /MVP-Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MVP-Sample/Other/Util/NameProvidable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NameProvidable.swift 3 | // MVP-Sample 4 | // 5 | // Created by NixonShih on 2018/11/19. 6 | // Copyright © 2018 NixonShih. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol NameProvidable { 12 | var name: String { get } 13 | } 14 | -------------------------------------------------------------------------------- /MVP-Sample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /MVP-Sample/DomainLayer/DataModel/BannerModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BannerModel.swift 3 | // MVP-Sample 4 | // 5 | // Created by Nixon.Shih on 2019/4/16. 6 | // Copyright © 2019 NixonShih. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct BannerModel: Codable { 12 | let imageName: String 13 | let url: URL? 14 | } 15 | 16 | -------------------------------------------------------------------------------- /MVP-Sample/DataLayer/DataModel/RemoteBannerModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RemoteBannerModel.swift 3 | // MVP-Sample 4 | // 5 | // Created by NixonShih on 2019/4/14. 6 | // Copyright © 2019 NixonShih. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct RemoteBannerModel: Codable { 12 | let imageName: String 13 | let url: String 14 | } 15 | -------------------------------------------------------------------------------- /MVP-Sample/DataLayer/DataModel/RemoteBaseModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RemoteBaseModel.swift 3 | // MVP-Sample 4 | // 5 | // Created by NixonShih on 2019/4/14. 6 | // Copyright © 2019 NixonShih. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct RemoteBaseModel: Codable { 12 | let code: Int 13 | let msg: String 14 | let body: T 15 | } 16 | -------------------------------------------------------------------------------- /MVP-Sample/ViewLayer/Scenes/SetReuseViewModelable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SetReuseViewModelable.swift 3 | // MVP-Sample 4 | // 5 | // Created by NixonShih on 2019/4/13. 6 | // Copyright © 2019 NixonShih. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol SetReuseViewModelable { 12 | 13 | associatedtype ViewModel 14 | func setReuseViewMode(_ viewModel: ViewModel) 15 | } 16 | -------------------------------------------------------------------------------- /MVP-Sample/Other/Extension/String+extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+extensions.swift 3 | // MVP-Sample 4 | // 5 | // Created by NixonShih on 2018/11/11. 6 | // Copyright © 2018年 NixonShih. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension String { 12 | 13 | func toBase64() -> String? { 14 | return data(using: .utf8)?.base64EncodedString() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/wp-contentuploads201903Nike-Cortez-Los-Angeles-CI9873-001-Release-Date-1.imageset/wp-contentuploads201903Nike-Cortez-Los-Angeles-CI9873-001-Release-Date-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/wp-contentuploads201903Nike-Cortez-Los-Angeles-CI9873-001-Release-Date-1.imageset/wp-contentuploads201903Nike-Cortez-Los-Angeles-CI9873-001-Release-Date-1.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/wp-contentuploads201903Nike-Cortez-Los-Angeles-CI9873-400-Release-Date-1.imageset/wp-contentuploads201903Nike-Cortez-Los-Angeles-CI9873-400-Release-Date-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/wp-contentuploads201903Nike-Cortez-Los-Angeles-CI9873-400-Release-Date-1.imageset/wp-contentuploads201903Nike-Cortez-Los-Angeles-CI9873-400-Release-Date-1.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/d3AtY29udGVudHVwbG9hZHMyMDE4MDdqb3JkYW4tbGVnYWN5LTMxMi1BUTQxNjAtMzAxLTEuanBn.imageset/d3AtY29udGVudHVwbG9hZHMyMDE4MDdqb3JkYW4tbGVnYWN5LTMxMi1BUTQxNjAtMzAxLTEuanBn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/d3AtY29udGVudHVwbG9hZHMyMDE4MDdqb3JkYW4tbGVnYWN5LTMxMi1BUTQxNjAtMzAxLTEuanBn.imageset/d3AtY29udGVudHVwbG9hZHMyMDE4MDdqb3JkYW4tbGVnYWN5LTMxMi1BUTQxNjAtMzAxLTEuanBn.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/d3AtY29udGVudHVwbG9hZHMyMDE4MTFOaWtlLUt5cmllLTUtVGFjby1BTzI5MTgtOTAyLmpwZw==.imageset/d3AtY29udGVudHVwbG9hZHMyMDE4MTFOaWtlLUt5cmllLTUtVGFjby1BTzI5MTgtOTAyLmpwZw==.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/d3AtY29udGVudHVwbG9hZHMyMDE4MTFOaWtlLUt5cmllLTUtVGFjby1BTzI5MTgtOTAyLmpwZw==.imageset/d3AtY29udGVudHVwbG9hZHMyMDE4MTFOaWtlLUt5cmllLTUtVGFjby1BTzI5MTgtOTAyLmpwZw==.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/12732.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "12732@3x.jpg", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/12746.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "12746@3x.jpg", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/12837.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "12837@3x.jpg", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/12841.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "12841@3x.jpg", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/9Osfhjm.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "9Osfhjm.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/JwKauWO.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "JwKauWO.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/N6OObvC.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "N6OObvC.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/QRdx9Il.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "QRdx9Il.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/TNOsGqn.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "TNOsGqn.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/cQnmyKQ.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "cQnmyKQ.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/mm6pknp.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "mm6pknp.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/o0l47Ca.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "o0l47Ca.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/p4Ud82Q.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "p4Ud82Q.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-SampleTests/Helper/Model/RemoteBannerModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RemoteBannerModel.swift 3 | // MVP-SampleTests 4 | // 5 | // Created by NixonShih on 2019/4/16. 6 | // Copyright © 2019 NixonShih. All rights reserved. 7 | // 8 | 9 | @testable import MVP_Sample 10 | 11 | extension RemoteBannerModel { 12 | 13 | static var newBanner: RemoteBannerModel { 14 | return RemoteBannerModel(imageName: "123", url: "345") 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/banner/banner1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "banner1.jpg", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/banner/banner2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "banner2.jpg", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/banner/banner3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "banner3.jpg", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/banner/banner4.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "banner4.jpg", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/banner/banner5.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "banner5.jpg", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/banner/banner6.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "banner6.jpg", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-0.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "frame-0.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "frame-1.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-10.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "frame-10.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-11.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "frame-11.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-12.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "frame-12.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-13.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "frame-13.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-14.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "frame-14.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-15.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "frame-15.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-16.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "frame-16.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-17.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "frame-17.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-18.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "frame-18.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-19.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "frame-19.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "frame-2.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-20.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "frame-20.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-21.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "frame-21.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-22.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "frame-22.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-23.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "frame-23.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-24.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "frame-24.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-25.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "frame-25.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-26.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "frame-26.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-27.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "frame-27.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-28.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "frame-28.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-29.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "frame-29.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "frame-3.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-4.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "frame-4.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-5.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "frame-5.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-6.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "frame-6.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-7.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "frame-7.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-8.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "frame-8.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/loading/frame-9.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "frame-9.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/wp-contentuploads201902nike-air-fear-of-god-180-black-AT8087-002-release-date-1.imageset/wp-contentuploads201902nike-air-fear-of-god-180-black-AT8087-002-release-date-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/wp-contentuploads201902nike-air-fear-of-god-180-black-AT8087-002-release-date-1.imageset/wp-contentuploads201902nike-air-fear-of-god-180-black-AT8087-002-release-date-1.jpg -------------------------------------------------------------------------------- /MVP-Sample/DomainLayer/DataModel/ShoesModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShoesModel.swift 3 | // MVP-Sample 4 | // 5 | // Created by NixonShih on 2018/11/10. 6 | // Copyright © 2018 NixonShih. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct ShoesModel: Codable { 12 | 13 | let date: String 14 | let description: String? 15 | var imageName: String 16 | let nickname: String? 17 | let price: Int 18 | let title: String 19 | } 20 | -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/d3AtY29udGVudHVwbG9hZHMyMDE4MTFhaXItam9yZGFuLTQtZG8tdGhlLXJpZ2h0LXRoaW5nLTIuanBn.imageset/d3AtY29udGVudHVwbG9hZHMyMDE4MTFhaXItam9yZGFuLTQtZG8tdGhlLXJpZ2h0LXRoaW5nLTIuanBn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/d3AtY29udGVudHVwbG9hZHMyMDE4MTFhaXItam9yZGFuLTQtZG8tdGhlLXJpZ2h0LXRoaW5nLTIuanBn.imageset/d3AtY29udGVudHVwbG9hZHMyMDE4MTFhaXItam9yZGFuLTQtZG8tdGhlLXJpZ2h0LXRoaW5nLTIuanBn.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/d3AtY29udGVudHVwbG9hZHMyMDE4MTFhaXItam9yZGFuLTQtZG8tdGhlLXJpZ2h0LXRoaW5nLTMuanBn.imageset/d3AtY29udGVudHVwbG9hZHMyMDE4MTFhaXItam9yZGFuLTQtZG8tdGhlLXJpZ2h0LXRoaW5nLTMuanBn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/d3AtY29udGVudHVwbG9hZHMyMDE4MTFhaXItam9yZGFuLTQtZG8tdGhlLXJpZ2h0LXRoaW5nLTMuanBn.imageset/d3AtY29udGVudHVwbG9hZHMyMDE4MTFhaXItam9yZGFuLTQtZG8tdGhlLXJpZ2h0LXRoaW5nLTMuanBn.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/wp-contentuploads201903Air-Jordan-4-GS-Monsoon-Blue-BQ9043-400-Release-Date-Price.imageset/wp-contentuploads201903Air-Jordan-4-GS-Monsoon-Blue-BQ9043-400-Release-Date-Price.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/wp-contentuploads201903Air-Jordan-4-GS-Monsoon-Blue-BQ9043-400-Release-Date-Price.imageset/wp-contentuploads201903Air-Jordan-4-GS-Monsoon-Blue-BQ9043-400-Release-Date-Price.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/wp-contentuploads201904Nike-Kyrie-5-Mamba-Mentality-AO2918-102-Release-Date-Price.imageset/wp-contentuploads201904Nike-Kyrie-5-Mamba-Mentality-AO2918-102-Release-Date-Price.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/wp-contentuploads201904Nike-Kyrie-5-Mamba-Mentality-AO2918-102-Release-Date-Price.imageset/wp-contentuploads201904Nike-Kyrie-5-Mamba-Mentality-AO2918-102-Release-Date-Price.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/M29vcEQ0Ry5qcGc=.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "M29vcEQ0Ry5qcGc=.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/MTE1MDZAM3guanBn.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "MTE1MDZAM3guanBn.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/MTIxNjhAM3guanBn.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "MTIxNjhAM3guanBn.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/RDdGVGV6eS5qcGc=.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "RDdGVGV6eS5qcGc=.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/DomainLayer/ShoesRepositorySpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShoesRepositorySpec.swift 3 | // MVP-Sample 4 | // 5 | // Created by Nixon.Shih on 2019/4/11. 6 | // Copyright © 2019 NixonShih. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol ShoesRepositorySpec { 12 | 13 | typealias FetchShoesCompletionHandler = (Result<[ShoesModel], Error>) -> () 14 | func fetchShoes(_ completionHandler: @escaping FetchShoesCompletionHandler) 15 | } 16 | -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autocys1ywaekpuvespws0x4lebron-16-remix-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autocys1ywaekpuvespws0x4lebron-16-remix-release-date.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autocys1ywaekpuvespws0x4lebron-16-remix-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autocys1ywaekpuvespws0x4lebron-16-remix-release-date.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/d3AtY29udGVudHVwbG9hZHMyMDE4MDRhaXItam9yZGFuLTEwLW9ybGFuZG8tcmVsZWFzZS1pbmZvLmpwZw==.imageset/d3AtY29udGVudHVwbG9hZHMyMDE4MDRhaXItam9yZGFuLTEwLW9ybGFuZG8tcmVsZWFzZS1pbmZvLmpwZw==.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/d3AtY29udGVudHVwbG9hZHMyMDE4MDRhaXItam9yZGFuLTEwLW9ybGFuZG8tcmVsZWFzZS1pbmZvLmpwZw==.imageset/d3AtY29udGVudHVwbG9hZHMyMDE4MDRhaXItam9yZGFuLTEwLW9ybGFuZG8tcmVsZWFzZS1pbmZvLmpwZw==.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/wp-contentuploads201903nike-air-fear-of-god-moccasin-black-at8086-002-release-date-1.imageset/wp-contentuploads201903nike-air-fear-of-god-moccasin-black-at8086-002-release-date-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/wp-contentuploads201903nike-air-fear-of-god-moccasin-black-at8086-002-release-date-1.imageset/wp-contentuploads201903nike-air-fear-of-god-moccasin-black-at8086-002-release-date-1.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autoxrj4afk5rpddzxbbx6wclebron-16-martin-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autoxrj4afk5rpddzxbbx6wclebron-16-martin-release-date.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autoxrj4afk5rpddzxbbx6wclebron-16-martin-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autoxrj4afk5rpddzxbbx6wclebron-16-martin-release-date.jpg -------------------------------------------------------------------------------- /MVP-Sample/DomainLayer/BannerRepositorySpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BannerRepositorySpec.swift 3 | // MVP-Sample 4 | // 5 | // Created by NixonShih on 2019/4/14. 6 | // Copyright © 2019 NixonShih. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol BannerRepositorySpec { 12 | 13 | typealias FetchBannerCompletionHandler = (Result<[RemoteBannerModel], Error>) -> () 14 | func fetchBanners(_ completionHandler: @escaping FetchBannerCompletionHandler) 15 | } 16 | -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autoegkplcxbgvbraxiwrkv4air-max2-light-atmos-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autoegkplcxbgvbraxiwrkv4air-max2-light-atmos-release-date.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autoegkplcxbgvbraxiwrkv4air-max2-light-atmos-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autoegkplcxbgvbraxiwrkv4air-max2-light-atmos-release-date.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autoq8vrexcyxpmj80u4esgvlebron-16-air-trainer-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autoq8vrexcyxpmj80u4esgvlebron-16-air-trainer-release-date.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autoq8vrexcyxpmj80u4esgvlebron-16-air-trainer-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autoq8vrexcyxpmj80u4esgvlebron-16-air-trainer-release-date.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autovziuczzeyzitgvjtar79air-max-1-on-air-tokyo-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autovziuczzeyzitgvjtar79air-max-1-on-air-tokyo-release-date.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autovziuczzeyzitgvjtar79air-max-1-on-air-tokyo-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autovziuczzeyzitgvjtar79air-max-1-on-air-tokyo-release-date.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autovztgxht8el3fnj7oe6uzair-max-98-on-air-nyc-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autovztgxht8el3fnj7oe6uzair-max-98-on-air-nyc-release-date.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autovztgxht8el3fnj7oe6uzair-max-98-on-air-nyc-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autovztgxht8el3fnj7oe6uzair-max-98-on-air-nyc-release-date.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/wp-contentuploads201903Air-Jordan-11-Low-Navy-Blue-Snakeskin-CD6846-102-Release-Date-Price.imageset/wp-contentuploads201903Air-Jordan-11-Low-Navy-Blue-Snakeskin-CD6846-102-Release-Date-Price.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/wp-contentuploads201903Air-Jordan-11-Low-Navy-Blue-Snakeskin-CD6846-102-Release-Date-Price.imageset/wp-contentuploads201903Air-Jordan-11-Low-Navy-Blue-Snakeskin-CD6846-102-Release-Date-Price.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autokqoxst75honzfjrnqqyjair-max-97-on-air-seoul-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autokqoxst75honzfjrnqqyjair-max-97-on-air-seoul-release-date.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autokqoxst75honzfjrnqqyjair-max-97-on-air-seoul-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autokqoxst75honzfjrnqqyjair-max-97-on-air-seoul-release-date.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/wp-contentuploads201901nike-air-fear-of-god-moccasin-pure-platinum-AT8086-001-release-date-1.imageset/wp-contentuploads201901nike-air-fear-of-god-moccasin-pure-platinum-AT8086-001-release-date-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/wp-contentuploads201901nike-air-fear-of-god-moccasin-pure-platinum-AT8086-001-release-date-1.imageset/wp-contentuploads201901nike-air-fear-of-god-moccasin-pure-platinum-AT8086-001-release-date-1.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_PDP_864_v1f_auto,b_rgb:f5f5f5ecliaenqoa7rgkgvp2xyair-jordan-1-low-mens-shoe-z3Tl2VeJ.imageset/aimagest_PDP_864_v1f_auto,b_rgb:f5f5f5ecliaenqoa7rgkgvp2xyair-jordan-1-low-mens-shoe-z3Tl2VeJ.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/aimagest_PDP_864_v1f_auto,b_rgb:f5f5f5ecliaenqoa7rgkgvp2xyair-jordan-1-low-mens-shoe-z3Tl2VeJ.imageset/aimagest_PDP_864_v1f_auto,b_rgb:f5f5f5ecliaenqoa7rgkgvp2xyair-jordan-1-low-mens-shoe-z3Tl2VeJ.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autopselea04ks9hnkcbsahdair-max-97-on-air-london-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autopselea04ks9hnkcbsahdair-max-97-on-air-london-release-date.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autopselea04ks9hnkcbsahdair-max-97-on-air-london-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autopselea04ks9hnkcbsahdair-max-97-on-air-london-release-date.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autovxm7ancrgvsdat5i7o0unike-pg-3-mamba-mentality-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autovxm7ancrgvsdat5i7o0unike-pg-3-mamba-mentality-release-date.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autovxm7ancrgvsdat5i7o0unike-pg-3-mamba-mentality-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autovxm7ancrgvsdat5i7o0unike-pg-3-mamba-mentality-release-date.jpg -------------------------------------------------------------------------------- /MVP-SampleTests/Helper/Model/BannerModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BannerModel.swift 3 | // MVP-SampleTests 4 | // 5 | // Created by NixonShih on 2019/4/16. 6 | // Copyright © 2019 NixonShih. All rights reserved. 7 | // 8 | 9 | @testable import MVP_Sample 10 | 11 | extension BannerModel: Equatable { 12 | 13 | public static func == (lhs: BannerModel, rhs: BannerModel) -> Bool { 14 | return lhs.imageName + (lhs.url?.absoluteString ?? "") == rhs.imageName + (rhs.url?.absoluteString ?? "") 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autobyweh2fwj8cxlvkcjyjbadapt-bb-future-of-the-game-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autobyweh2fwj8cxlvkcjyjbadapt-bb-future-of-the-game-release-date.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autobyweh2fwj8cxlvkcjyjbadapt-bb-future-of-the-game-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autobyweh2fwj8cxlvkcjyjbadapt-bb-future-of-the-game-release-date.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autotunyzqjx91k3yk55mggeair-max2-light-purple-berry-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autotunyzqjx91k3yk55mggeair-max2-light-purple-berry-release-date.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autotunyzqjx91k3yk55mggeair-max2-light-purple-berry-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autotunyzqjx91k3yk55mggeair-max2-light-purple-berry-release-date.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autourcu79fr3do9u0a4soicair-max-97-on-air-shanghai-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autourcu79fr3do9u0a4soicair-max-97-on-air-shanghai-release-date.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autourcu79fr3do9u0a4soicair-max-97-on-air-shanghai-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autourcu79fr3do9u0a4soicair-max-97-on-air-shanghai-release-date.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autoepba4fhpfb9ajdmpmea2air-foamposite-hyper-crimson-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autoepba4fhpfb9ajdmpmea2air-foamposite-hyper-crimson-release-date.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autoepba4fhpfb9ajdmpmea2air-foamposite-hyper-crimson-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autoepba4fhpfb9ajdmpmea2air-foamposite-hyper-crimson-release-date.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/solelinksstoragereleaseImages3666AQ1090-200_sivasdescalzo-nike-NIKE-REACT-ELEMENT-87-AQ1090-200-1.imageset/solelinksstoragereleaseImages3666AQ1090-200_sivasdescalzo-nike-NIKE-REACT-ELEMENT-87-AQ1090-200-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/solelinksstoragereleaseImages3666AQ1090-200_sivasdescalzo-nike-NIKE-REACT-ELEMENT-87-AQ1090-200-1.imageset/solelinksstoragereleaseImages3666AQ1090-200_sivasdescalzo-nike-NIKE-REACT-ELEMENT-87-AQ1090-200-1.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/solelinksstoragereleaseImages3667AQ1090-300_sivasdescalzo-nike-NIKE-REACT-ELEMENT-87-AQ1090-300-1.imageset/solelinksstoragereleaseImages3667AQ1090-300_sivasdescalzo-nike-NIKE-REACT-ELEMENT-87-AQ1090-300-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/solelinksstoragereleaseImages3667AQ1090-300_sivasdescalzo-nike-NIKE-REACT-ELEMENT-87-AQ1090-300-1.imageset/solelinksstoragereleaseImages3667AQ1090-300_sivasdescalzo-nike-NIKE-REACT-ELEMENT-87-AQ1090-300-1.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_lsw_1920,c_limit,f_autodil46qlauylplvdx6dudair-foamposite-hyper-crimson-release-date.imageset/aimagest_prod_lsw_1920,c_limit,f_autodil46qlauylplvdx6dudair-foamposite-hyper-crimson-release-date.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_lsw_1920,c_limit,f_autodil46qlauylplvdx6dudair-foamposite-hyper-crimson-release-date.imageset/aimagest_prod_lsw_1920,c_limit,f_autodil46qlauylplvdx6dudair-foamposite-hyper-crimson-release-date.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_lsw_1920,c_limit,f_autovkzcokvv4fh2jjk71djdair-foamposite-hyper-crimson-release-date.imageset/aimagest_prod_lsw_1920,c_limit,f_autovkzcokvv4fh2jjk71djdair-foamposite-hyper-crimson-release-date.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_lsw_1920,c_limit,f_autovkzcokvv4fh2jjk71djdair-foamposite-hyper-crimson-release-date.imageset/aimagest_prod_lsw_1920,c_limit,f_autovkzcokvv4fh2jjk71djdair-foamposite-hyper-crimson-release-date.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_lsw_1920,c_limit,f_autoy0udif4vqu87bklmxs9uair-foamposite-hyper-crimson-release-date.imageset/aimagest_prod_lsw_1920,c_limit,f_autoy0udif4vqu87bklmxs9uair-foamposite-hyper-crimson-release-date.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_lsw_1920,c_limit,f_autoy0udif4vqu87bklmxs9uair-foamposite-hyper-crimson-release-date.imageset/aimagest_prod_lsw_1920,c_limit,f_autoy0udif4vqu87bklmxs9uair-foamposite-hyper-crimson-release-date.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autoxcsral03nckwhqi3vshbair-vapormax-plus-on-air-paris-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autoxcsral03nckwhqi3vshbair-vapormax-plus-on-air-paris-release-date.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autoxcsral03nckwhqi3vshbair-vapormax-plus-on-air-paris-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autoxcsral03nckwhqi3vshbair-vapormax-plus-on-air-paris-release-date.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autocr2smyshlm9vamsquxamnike-blazer-mid-77-black-canvas-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autocr2smyshlm9vamsquxamnike-blazer-mid-77-black-canvas-release-date.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autocr2smyshlm9vamsquxamnike-blazer-mid-77-black-canvas-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autocr2smyshlm9vamsquxamnike-blazer-mid-77-black-canvas-release-date.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autot8cz8v6lx119os3pn94onike-air-trainer-3-medicine-ball-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autot8cz8v6lx119os3pn94onike-air-trainer-3-medicine-ball-release-date.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autot8cz8v6lx119os3pn94onike-air-trainer-3-medicine-ball-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autot8cz8v6lx119os3pn94onike-air-trainer-3-medicine-ball-release-date.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autoyqhwscvw99o0a1w6pmwiwomens-air-jordan-12-midnight-black-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autoyqhwscvw99o0a1w6pmwiwomens-air-jordan-12-midnight-black-release-date.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autoyqhwscvw99o0a1w6pmwiwomens-air-jordan-12-midnight-black-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autoyqhwscvw99o0a1w6pmwiwomens-air-jordan-12-midnight-black-release-date.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/d3AtY29udGVudHVwbG9hZHMyMDE4MDZOaWtlLUt5cmllLTQtVHJpcGxlLUJsYWNrLTk0MzgwNy0wMDgtUmVsZWFzZS1EYXRlLmpwZw==.imageset/d3AtY29udGVudHVwbG9hZHMyMDE4MDZOaWtlLUt5cmllLTQtVHJpcGxlLUJsYWNrLTk0MzgwNy0wMDgtUmVsZWFzZS1EYXRlLmpwZw==.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/d3AtY29udGVudHVwbG9hZHMyMDE4MDZOaWtlLUt5cmllLTQtVHJpcGxlLUJsYWNrLTk0MzgwNy0wMDgtUmVsZWFzZS1EYXRlLmpwZw==.imageset/d3AtY29udGVudHVwbG9hZHMyMDE4MDZOaWtlLUt5cmllLTQtVHJpcGxlLUJsYWNrLTk0MzgwNy0wMDgtUmVsZWFzZS1EYXRlLmpwZw==.jpg -------------------------------------------------------------------------------- /MVP-Sample/DomainLayer/UseCases/FetchDataUseCaseSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FetchDataUseCaseSpec.swift 3 | // MVP-Sample 4 | // 5 | // Created by Nixon.Shih on 2019/4/16. 6 | // Copyright © 2019 NixonShih. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol FetchDataUseCaseSpec { 12 | 13 | associatedtype DataModel 14 | 15 | typealias FetchDataModelUseCaseCompletionHandler = (_ books: Result) -> () 16 | func fetchDataModel(_ completionHandler: @escaping FetchDataModelUseCaseCompletionHandler) 17 | } 18 | -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autojejlqawpimm6qoanfwzcnike-blazer-mid-77-vintage-pink-foam-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autojejlqawpimm6qoanfwzcnike-blazer-mid-77-vintage-pink-foam-release-date.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autojejlqawpimm6qoanfwzcnike-blazer-mid-77-vintage-pink-foam-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autojejlqawpimm6qoanfwzcnike-blazer-mid-77-vintage-pink-foam-release-date.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autovp6fcofbqm8skzhjuldmnike-blazer-mid-77-vintage-sail-white-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autovp6fcofbqm8skzhjuldmnike-blazer-mid-77-vintage-sail-white-release-date.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autovp6fcofbqm8skzhjuldmnike-blazer-mid-77-vintage-sail-white-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autovp6fcofbqm8skzhjuldmnike-blazer-mid-77-vintage-sail-white-release-date.jpg -------------------------------------------------------------------------------- /MVP-Sample/Other/Constants/ThemeColor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ThemeColor.swift 3 | // MVP-Sample 4 | // 5 | // Created by Nixon.Shih on 2018/11/9. 6 | // Copyright © 2018 NixonShih. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | enum ThemeColor: String { 12 | 13 | case pureBlack = "000000" 14 | case pureWhite = "ffffff" 15 | case darkBlack = "252525" 16 | case darkRed = "b41b1b" 17 | 18 | var color: UIColor { 19 | let hex = Int(self.rawValue, radix: 16) 20 | return UIColor(rgb: hex ?? 0x000000) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /MVP-Sample/Other/Util/ProjectNameHelper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProjectNameHelper.swift 3 | // MVP-Sample 4 | // 5 | // Created by NixonShih on 2018/11/19. 6 | // Copyright © 2018 NixonShih. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct ProjectNameHelper: NameProvidable { 12 | 13 | var name: String { 14 | guard 15 | let info = Bundle.main.infoDictionary, 16 | let bundleName = info["CFBundleName"] as? String else { 17 | fatalError() 18 | } 19 | return bundleName 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/wp-contentuploads201903Nike-Cortez-Los-Angeles-CI9873-001-Release-Date-1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "wp-contentuploads201903Nike-Cortez-Los-Angeles-CI9873-001-Release-Date-1.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/wp-contentuploads201903Nike-Cortez-Los-Angeles-CI9873-400-Release-Date-1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "wp-contentuploads201903Nike-Cortez-Los-Angeles-CI9873-400-Release-Date-1.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/bWVkaWFjYXRhbG9ncHJvZHVjdGNhY2hlMWJhc2UxMDAweDYwMDlkZjc4ZWFiMzM1MjVkMDhkNmU1ZmI4ZDI3MTM2ZTk1RUVFRTc1NzlfMS5wbmc=.imageset/bWVkaWFjYXRhbG9ncHJvZHVjdGNhY2hlMWJhc2UxMDAweDYwMDlkZjc4ZWFiMzM1MjVkMDhkNmU1ZmI4ZDI3MTM2ZTk1RUVFRTc1NzlfMS5wbmc=.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/bWVkaWFjYXRhbG9ncHJvZHVjdGNhY2hlMWJhc2UxMDAweDYwMDlkZjc4ZWFiMzM1MjVkMDhkNmU1ZmI4ZDI3MTM2ZTk1RUVFRTc1NzlfMS5wbmc=.imageset/bWVkaWFjYXRhbG9ncHJvZHVjdGNhY2hlMWJhc2UxMDAweDYwMDlkZjc4ZWFiMzM1MjVkMDhkNmU1ZmI4ZDI3MTM2ZTk1RUVFRTc1NzlfMS5wbmc=.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/bWVkaWFjYXRhbG9ncHJvZHVjdGNhY2hlMWJhc2UxMDAweDYwMDlkZjc4ZWFiMzM1MjVkMDhkNmU1ZmI4ZDI3MTM2ZTk1RUVFRTc1ODNfMS5wbmc=.imageset/bWVkaWFjYXRhbG9ncHJvZHVjdGNhY2hlMWJhc2UxMDAweDYwMDlkZjc4ZWFiMzM1MjVkMDhkNmU1ZmI4ZDI3MTM2ZTk1RUVFRTc1ODNfMS5wbmc=.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/bWVkaWFjYXRhbG9ncHJvZHVjdGNhY2hlMWJhc2UxMDAweDYwMDlkZjc4ZWFiMzM1MjVkMDhkNmU1ZmI4ZDI3MTM2ZTk1RUVFRTc1ODNfMS5wbmc=.imageset/bWVkaWFjYXRhbG9ncHJvZHVjdGNhY2hlMWJhc2UxMDAweDYwMDlkZjc4ZWFiMzM1MjVkMDhkNmU1ZmI4ZDI3MTM2ZTk1RUVFRTc1ODNfMS5wbmc=.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/d3AtY29udGVudHVwbG9hZHMyMDE4MDdqb3JkYW4tbGVnYWN5LTMxMi1BUTQxNjAtMzAxLTEuanBn.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "d3AtY29udGVudHVwbG9hZHMyMDE4MDdqb3JkYW4tbGVnYWN5LTMxMi1BUTQxNjAtMzAxLTEuanBn.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/d3AtY29udGVudHVwbG9hZHMyMDE4MTFOaWtlLUt5cmllLTUtVGFjby1BTzI5MTgtOTAyLmpwZw==.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "d3AtY29udGVudHVwbG9hZHMyMDE4MTFOaWtlLUt5cmllLTUtVGFjby1BTzI5MTgtOTAyLmpwZw==.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/wp-contentuploads201902nike-air-fear-of-god-180-black-AT8087-002-release-date-1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "wp-contentuploads201902nike-air-fear-of-god-180-black-AT8087-002-release-date-1.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autoeyeg1kej2njudfya2fhqnike-womens-air-max-95-plant-color-collection-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autoeyeg1kej2njudfya2fhqnike-womens-air-max-95-plant-color-collection-release-date.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autoeyeg1kej2njudfya2fhqnike-womens-air-max-95-plant-color-collection-release-date.imageset/aimagest_prod_ssw_960,c_limit,f_autoeyeg1kej2njudfya2fhqnike-womens-air-max-95-plant-color-collection-release-date.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/d3AtY29udGVudHVwbG9hZHMyMDE4MTFhaXItam9yZGFuLTQtZG8tdGhlLXJpZ2h0LXRoaW5nLTIuanBn.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "d3AtY29udGVudHVwbG9hZHMyMDE4MTFhaXItam9yZGFuLTQtZG8tdGhlLXJpZ2h0LXRoaW5nLTIuanBn.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/d3AtY29udGVudHVwbG9hZHMyMDE4MTFhaXItam9yZGFuLTQtZG8tdGhlLXJpZ2h0LXRoaW5nLTMuanBn.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "d3AtY29udGVudHVwbG9hZHMyMDE4MTFhaXItam9yZGFuLTQtZG8tdGhlLXJpZ2h0LXRoaW5nLTMuanBn.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/wp-contentuploads201903Air-Jordan-4-GS-Monsoon-Blue-BQ9043-400-Release-Date-Price.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "wp-contentuploads201903Air-Jordan-4-GS-Monsoon-Blue-BQ9043-400-Release-Date-Price.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/wp-contentuploads201904Nike-Kyrie-5-Mamba-Mentality-AO2918-102-Release-Date-Price.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "wp-contentuploads201904Nike-Kyrie-5-Mamba-Mentality-AO2918-102-Release-Date-Price.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autocys1ywaekpuvespws0x4lebron-16-remix-release-date.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "aimagest_prod_ssw_960,c_limit,f_autocys1ywaekpuvespws0x4lebron-16-remix-release-date.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/bWVkaWFjYXRhbG9ncHJvZHVjdGNhY2hlMWJhc2UxMDAweDYwMDlkZjc4ZWFiMzM1MjVkMDhkNmU1ZmI4ZDI3MTM2ZTk1ZWVlZTc1ODFfMV8xLnBuZw==.imageset/bWVkaWFjYXRhbG9ncHJvZHVjdGNhY2hlMWJhc2UxMDAweDYwMDlkZjc4ZWFiMzM1MjVkMDhkNmU1ZmI4ZDI3MTM2ZTk1ZWVlZTc1ODFfMV8xLnBuZw==.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/bWVkaWFjYXRhbG9ncHJvZHVjdGNhY2hlMWJhc2UxMDAweDYwMDlkZjc4ZWFiMzM1MjVkMDhkNmU1ZmI4ZDI3MTM2ZTk1ZWVlZTc1ODFfMV8xLnBuZw==.imageset/bWVkaWFjYXRhbG9ncHJvZHVjdGNhY2hlMWJhc2UxMDAweDYwMDlkZjc4ZWFiMzM1MjVkMDhkNmU1ZmI4ZDI3MTM2ZTk1ZWVlZTc1ODFfMV8xLnBuZw==.png -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/d3AtY29udGVudHVwbG9hZHMyMDE4MDRhaXItam9yZGFuLTEwLW9ybGFuZG8tcmVsZWFzZS1pbmZvLmpwZw==.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "d3AtY29udGVudHVwbG9hZHMyMDE4MDRhaXItam9yZGFuLTEwLW9ybGFuZG8tcmVsZWFzZS1pbmZvLmpwZw==.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/d3AtY29udGVudHVwbG9hZHMyMDE4MDZKb3JkYW4tTGVnYWN5LTMxMi1TdG9ybS1CbHVlLVJvYXlsLUFRNDE2MC0xMDQtUmVsZWFzZS1EYXRlLmpwZw==.imageset/d3AtY29udGVudHVwbG9hZHMyMDE4MDZKb3JkYW4tTGVnYWN5LTMxMi1TdG9ybS1CbHVlLVJvYXlsLUFRNDE2MC0xMDQtUmVsZWFzZS1EYXRlLmpwZw==.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/d3AtY29udGVudHVwbG9hZHMyMDE4MDZKb3JkYW4tTGVnYWN5LTMxMi1TdG9ybS1CbHVlLVJvYXlsLUFRNDE2MC0xMDQtUmVsZWFzZS1EYXRlLmpwZw==.imageset/d3AtY29udGVudHVwbG9hZHMyMDE4MDZKb3JkYW4tTGVnYWN5LTMxMi1TdG9ybS1CbHVlLVJvYXlsLUFRNDE2MC0xMDQtUmVsZWFzZS1EYXRlLmpwZw==.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/wp-contentuploads201903nike-air-fear-of-god-moccasin-black-at8086-002-release-date-1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "wp-contentuploads201903nike-air-fear-of-god-moccasin-black-at8086-002-release-date-1.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autoxrj4afk5rpddzxbbx6wclebron-16-martin-release-date.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "aimagest_prod_ssw_960,c_limit,f_autoxrj4afk5rpddzxbbx6wclebron-16-martin-release-date.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autoegkplcxbgvbraxiwrkv4air-max2-light-atmos-release-date.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "aimagest_prod_ssw_960,c_limit,f_autoegkplcxbgvbraxiwrkv4air-max2-light-atmos-release-date.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autokqoxst75honzfjrnqqyjair-max-97-on-air-seoul-release-date.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "aimagest_prod_ssw_960,c_limit,f_autokqoxst75honzfjrnqqyjair-max-97-on-air-seoul-release-date.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autoq8vrexcyxpmj80u4esgvlebron-16-air-trainer-release-date.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "aimagest_prod_ssw_960,c_limit,f_autoq8vrexcyxpmj80u4esgvlebron-16-air-trainer-release-date.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autovziuczzeyzitgvjtar79air-max-1-on-air-tokyo-release-date.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "aimagest_prod_ssw_960,c_limit,f_autovziuczzeyzitgvjtar79air-max-1-on-air-tokyo-release-date.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autovztgxht8el3fnj7oe6uzair-max-98-on-air-nyc-release-date.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "aimagest_prod_ssw_960,c_limit,f_autovztgxht8el3fnj7oe6uzair-max-98-on-air-nyc-release-date.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/wp-contentuploads201901nike-air-fear-of-god-moccasin-pure-platinum-AT8086-001-release-date-1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "wp-contentuploads201901nike-air-fear-of-god-moccasin-pure-platinum-AT8086-001-release-date-1.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/wp-contentuploads201903Air-Jordan-11-Low-Navy-Blue-Snakeskin-CD6846-102-Release-Date-Price.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "wp-contentuploads201903Air-Jordan-11-Low-Navy-Blue-Snakeskin-CD6846-102-Release-Date-Price.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_PDP_864_v1f_auto,b_rgb:f5f5f5ecliaenqoa7rgkgvp2xyair-jordan-1-low-mens-shoe-z3Tl2VeJ.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "aimagest_PDP_864_v1f_auto,b_rgb:f5f5f5ecliaenqoa7rgkgvp2xyair-jordan-1-low-mens-shoe-z3Tl2VeJ.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autopselea04ks9hnkcbsahdair-max-97-on-air-london-release-date.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "aimagest_prod_ssw_960,c_limit,f_autopselea04ks9hnkcbsahdair-max-97-on-air-london-release-date.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autovxm7ancrgvsdat5i7o0unike-pg-3-mamba-mentality-release-date.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "aimagest_prod_ssw_960,c_limit,f_autovxm7ancrgvsdat5i7o0unike-pg-3-mamba-mentality-release-date.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/d3AtY29udGVudHVwbG9hZHMyMDE4MTFOaWtlLUFpci1Gb3JjZS0yNzAtVXRpbGl0eS1CbGFjay1Wb2x0LUFRMDU3Mi0wMDEtUmVsZWFzZS1EYXRlLmpwZw==.imageset/d3AtY29udGVudHVwbG9hZHMyMDE4MTFOaWtlLUFpci1Gb3JjZS0yNzAtVXRpbGl0eS1CbGFjay1Wb2x0LUFRMDU3Mi0wMDEtUmVsZWFzZS1EYXRlLmpwZw==.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/d3AtY29udGVudHVwbG9hZHMyMDE4MTFOaWtlLUFpci1Gb3JjZS0yNzAtVXRpbGl0eS1CbGFjay1Wb2x0LUFRMDU3Mi0wMDEtUmVsZWFzZS1EYXRlLmpwZw==.imageset/d3AtY29udGVudHVwbG9hZHMyMDE4MTFOaWtlLUFpci1Gb3JjZS0yNzAtVXRpbGl0eS1CbGFjay1Wb2x0LUFRMDU3Mi0wMDEtUmVsZWFzZS1EYXRlLmpwZw==.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autobyweh2fwj8cxlvkcjyjbadapt-bb-future-of-the-game-release-date.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "aimagest_prod_ssw_960,c_limit,f_autobyweh2fwj8cxlvkcjyjbadapt-bb-future-of-the-game-release-date.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autoepba4fhpfb9ajdmpmea2air-foamposite-hyper-crimson-release-date.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "aimagest_prod_ssw_960,c_limit,f_autoepba4fhpfb9ajdmpmea2air-foamposite-hyper-crimson-release-date.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autotunyzqjx91k3yk55mggeair-max2-light-purple-berry-release-date.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "aimagest_prod_ssw_960,c_limit,f_autotunyzqjx91k3yk55mggeair-max2-light-purple-berry-release-date.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autourcu79fr3do9u0a4soicair-max-97-on-air-shanghai-release-date.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "aimagest_prod_ssw_960,c_limit,f_autourcu79fr3do9u0a4soicair-max-97-on-air-shanghai-release-date.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/solelinksstoragereleaseImages3666AQ1090-200_sivasdescalzo-nike-NIKE-REACT-ELEMENT-87-AQ1090-200-1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "solelinksstoragereleaseImages3666AQ1090-200_sivasdescalzo-nike-NIKE-REACT-ELEMENT-87-AQ1090-200-1.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/solelinksstoragereleaseImages3667AQ1090-300_sivasdescalzo-nike-NIKE-REACT-ELEMENT-87-AQ1090-300-1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "solelinksstoragereleaseImages3667AQ1090-300_sivasdescalzo-nike-NIKE-REACT-ELEMENT-87-AQ1090-300-1.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/DataLayer/Repositories/Remote/NetworkFetchable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkFetchable.swift 3 | // MVP-Sample 4 | // 5 | // Created by NixonShih on 2018/11/19. 6 | // Copyright © 2018 NixonShih. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum NetworkError: Error { 12 | case url 13 | case encode 14 | case connection 15 | case decode 16 | case emptyContent 17 | case serviceError 18 | } 19 | 20 | protocol NetworkFetchable { 21 | 22 | associatedtype DataModel: Codable 23 | func fire(_ completionHandler: @escaping (Result) -> ()) 24 | } 25 | -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_lsw_1920,c_limit,f_autodil46qlauylplvdx6dudair-foamposite-hyper-crimson-release-date.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "aimagest_prod_lsw_1920,c_limit,f_autodil46qlauylplvdx6dudair-foamposite-hyper-crimson-release-date.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_lsw_1920,c_limit,f_autovkzcokvv4fh2jjk71djdair-foamposite-hyper-crimson-release-date.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "aimagest_prod_lsw_1920,c_limit,f_autovkzcokvv4fh2jjk71djdair-foamposite-hyper-crimson-release-date.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_lsw_1920,c_limit,f_autoy0udif4vqu87bklmxs9uair-foamposite-hyper-crimson-release-date.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "aimagest_prod_lsw_1920,c_limit,f_autoy0udif4vqu87bklmxs9uair-foamposite-hyper-crimson-release-date.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autoxcsral03nckwhqi3vshbair-vapormax-plus-on-air-paris-release-date.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "aimagest_prod_ssw_960,c_limit,f_autoxcsral03nckwhqi3vshbair-vapormax-plus-on-air-paris-release-date.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc2ZfYXV0byxiX3JnYjpmNWY1ZjUsd180NDBuenlibnRkZDNhY3B3YW1xYXFycWNvcnRlei1iYXNpYy1sZWF0aGVyLW1lbnMtc2hvZS1SUFBDRzUuanBn.imageset/YWltYWdlc2ZfYXV0byxiX3JnYjpmNWY1ZjUsd180NDBuenlibnRkZDNhY3B3YW1xYXFycWNvcnRlei1iYXNpYy1sZWF0aGVyLW1lbnMtc2hvZS1SUFBDRzUuanBn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc2ZfYXV0byxiX3JnYjpmNWY1ZjUsd180NDBuenlibnRkZDNhY3B3YW1xYXFycWNvcnRlei1iYXNpYy1sZWF0aGVyLW1lbnMtc2hvZS1SUFBDRzUuanBn.imageset/YWltYWdlc2ZfYXV0byxiX3JnYjpmNWY1ZjUsd180NDBuenlibnRkZDNhY3B3YW1xYXFycWNvcnRlei1iYXNpYy1sZWF0aGVyLW1lbnMtc2hvZS1SUFBDRzUuanBn.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autocr2smyshlm9vamsquxamnike-blazer-mid-77-black-canvas-release-date.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "aimagest_prod_ssw_960,c_limit,f_autocr2smyshlm9vamsquxamnike-blazer-mid-77-black-canvas-release-date.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autot8cz8v6lx119os3pn94onike-air-trainer-3-medicine-ball-release-date.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "aimagest_prod_ssw_960,c_limit,f_autot8cz8v6lx119os3pn94onike-air-trainer-3-medicine-ball-release-date.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autoyqhwscvw99o0a1w6pmwiwomens-air-jordan-12-midnight-black-release-date.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "aimagest_prod_ssw_960,c_limit,f_autoyqhwscvw99o0a1w6pmwiwomens-air-jordan-12-midnight-black-release-date.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/d3AtY29udGVudHVwbG9hZHMyMDE4MDZOaWtlLUt5cmllLTQtVHJpcGxlLUJsYWNrLTk0MzgwNy0wMDgtUmVsZWFzZS1EYXRlLmpwZw==.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "d3AtY29udGVudHVwbG9hZHMyMDE4MDZOaWtlLUt5cmllLTQtVHJpcGxlLUJsYWNrLTk0MzgwNy0wMDgtUmVsZWFzZS1EYXRlLmpwZw==.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-SampleTests/Helper/Repository/MockFetchShoesRepository.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FetchShoesRepository.swift 3 | // MVP-SampleTests 4 | // 5 | // Created by NixonShih on 2019/4/16. 6 | // Copyright © 2019 NixonShih. All rights reserved. 7 | // 8 | 9 | @testable import MVP_Sample 10 | 11 | enum MockFetchShoesRepositoryError: Error { 12 | case fail 13 | } 14 | 15 | class MockFetchShoesRepository: ShoesRepositorySpec { 16 | 17 | var expectedResult: Result<[ShoesModel], Error>! 18 | 19 | func fetchShoes(_ completionHandler: @escaping FetchShoesCompletionHandler) { 20 | completionHandler(expectedResult) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autojejlqawpimm6qoanfwzcnike-blazer-mid-77-vintage-pink-foam-release-date.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "aimagest_prod_ssw_960,c_limit,f_autojejlqawpimm6qoanfwzcnike-blazer-mid-77-vintage-pink-foam-release-date.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autovp6fcofbqm8skzhjuldmnike-blazer-mid-77-vintage-sail-white-release-date.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "aimagest_prod_ssw_960,c_limit,f_autovp6fcofbqm8skzhjuldmnike-blazer-mid-77-vintage-sail-white-release-date.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/bWVkaWFjYXRhbG9ncHJvZHVjdGNhY2hlMWJhc2UxMDAweDYwMDlkZjc4ZWFiMzM1MjVkMDhkNmU1ZmI4ZDI3MTM2ZTk1RUVFRTc1NzlfMS5wbmc=.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "bWVkaWFjYXRhbG9ncHJvZHVjdGNhY2hlMWJhc2UxMDAweDYwMDlkZjc4ZWFiMzM1MjVkMDhkNmU1ZmI4ZDI3MTM2ZTk1RUVFRTc1NzlfMS5wbmc=.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/bWVkaWFjYXRhbG9ncHJvZHVjdGNhY2hlMWJhc2UxMDAweDYwMDlkZjc4ZWFiMzM1MjVkMDhkNmU1ZmI4ZDI3MTM2ZTk1RUVFRTc1ODNfMS5wbmc=.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "bWVkaWFjYXRhbG9ncHJvZHVjdGNhY2hlMWJhc2UxMDAweDYwMDlkZjc4ZWFiMzM1MjVkMDhkNmU1ZmI4ZDI3MTM2ZTk1RUVFRTc1ODNfMS5wbmc=.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/ViewLayer/Scenes/ShoesList/Banner/ShoesListBannerCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShoesListBannerCollectionViewCell.swift 3 | // MVP-Sample 4 | // 5 | // Created by NixonShih on 2019/4/14. 6 | // Copyright © 2019 NixonShih. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ShoesListBannerCollectionViewCell: UICollectionViewCell, SetReuseViewModelable { 12 | 13 | @IBOutlet weak var bannerImageView: UIImageView! 14 | 15 | func setReuseViewMode(_ viewModel: String) { 16 | bannerImageView.image = UIImage(named: viewModel) 17 | bannerImageView.contentMode = .scaleAspectFit 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/aimagest_prod_ssw_960,c_limit,f_autoeyeg1kej2njudfya2fhqnike-womens-air-max-95-plant-color-collection-release-date.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "aimagest_prod_ssw_960,c_limit,f_autoeyeg1kej2njudfya2fhqnike-womens-air-max-95-plant-color-collection-release-date.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/bWVkaWFjYXRhbG9ncHJvZHVjdGNhY2hlMWJhc2UxMDAweDYwMDlkZjc4ZWFiMzM1MjVkMDhkNmU1ZmI4ZDI3MTM2ZTk1ZWVlZTc1ODFfMV8xLnBuZw==.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "bWVkaWFjYXRhbG9ncHJvZHVjdGNhY2hlMWJhc2UxMDAweDYwMDlkZjc4ZWFiMzM1MjVkMDhkNmU1ZmI4ZDI3MTM2ZTk1ZWVlZTc1ODFfMV8xLnBuZw==.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/d3AtY29udGVudHVwbG9hZHMyMDE4MDZKb3JkYW4tTGVnYWN5LTMxMi1TdG9ybS1CbHVlLVJvYXlsLUFRNDE2MC0xMDQtUmVsZWFzZS1EYXRlLmpwZw==.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "d3AtY29udGVudHVwbG9hZHMyMDE4MDZKb3JkYW4tTGVnYWN5LTMxMi1TdG9ybS1CbHVlLVJvYXlsLUFRNDE2MC0xMDQtUmVsZWFzZS1EYXRlLmpwZw==.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/d3d3LnNuZWFrZXJlbGl0ZS5jb213cC1jb250ZW50dXBsb2FkczIwMTgwNmFkaWRhcy1ZZWV6eS1Cb29zdC0zNTAtVjItQnV0dGVyLVJlbGVhc2UtRGF0ZS1GMzY5ODAuanBn.imageset/d3d3LnNuZWFrZXJlbGl0ZS5jb213cC1jb250ZW50dXBsb2FkczIwMTgwNmFkaWRhcy1ZZWV6eS1Cb29zdC0zNTAtVjItQnV0dGVyLVJlbGVhc2UtRGF0ZS1GMzY5ODAuanBn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/d3d3LnNuZWFrZXJlbGl0ZS5jb213cC1jb250ZW50dXBsb2FkczIwMTgwNmFkaWRhcy1ZZWV6eS1Cb29zdC0zNTAtVjItQnV0dGVyLVJlbGVhc2UtRGF0ZS1GMzY5ODAuanBn.imageset/d3d3LnNuZWFrZXJlbGl0ZS5jb213cC1jb250ZW50dXBsb2FkczIwMTgwNmFkaWRhcy1ZZWV6eS1Cb29zdC0zNTAtVjItQnV0dGVyLVJlbGVhc2UtRGF0ZS1GMzY5ODAuanBn.jpg -------------------------------------------------------------------------------- /MVP-Sample/ViewLayer/Scenes/ViewBuilderSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewBuilderSpec.swift 3 | // MVP-Sample 4 | // 5 | // Created by Nixon.Shih on 2019/4/12. 6 | // Copyright © 2019 NixonShih. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol ViewBuilderSpec { 12 | 13 | associatedtype ViewType: UIViewController 14 | 15 | func build() -> ViewType 16 | func buildWithNavigationController() -> BaseNavigationController 17 | } 18 | 19 | extension ViewBuilderSpec { 20 | 21 | func buildWithNavigationController() -> BaseNavigationController { 22 | return BaseNavigationController(rootViewController: build()) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /MVP-SampleTests/Helper/Repository/MockRemoteBannerRepository.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockRemoteBannerRepository.swift 3 | // MVP-SampleTests 4 | // 5 | // Created by NixonShih on 2019/4/16. 6 | // Copyright © 2019 NixonShih. All rights reserved. 7 | // 8 | 9 | @testable import MVP_Sample 10 | 11 | enum MockRemoteBannerRepositoryError: Error { 12 | case fail 13 | } 14 | 15 | class MockRemoteBannerRepository: BannerRepositorySpec { 16 | 17 | func fetchBanners(_ completionHandler: @escaping FetchBannerCompletionHandler) { 18 | completionHandler(expectedResult) 19 | } 20 | 21 | var expectedResult: Result<[RemoteBannerModel], Error>! 22 | } 23 | -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/d3AtY29udGVudHVwbG9hZHMyMDE4MTFOaWtlLUFpci1Gb3JjZS0yNzAtVXRpbGl0eS1CbGFjay1Wb2x0LUFRMDU3Mi0wMDEtUmVsZWFzZS1EYXRlLmpwZw==.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "d3AtY29udGVudHVwbG9hZHMyMDE4MTFOaWtlLUFpci1Gb3JjZS0yNzAtVXRpbGl0eS1CbGFjay1Wb2x0LUFRMDU3Mi0wMDEtUmVsZWFzZS1EYXRlLmpwZw==.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc2ZfYXV0byxiX3JnYjpmNWY1ZjUsd180NDBmc29sM2I3aGUxcnJkYmpsbTV4MGFpci1mb290c2NhcGUtbWlkLXV0aWxpdHktZG0tbWVucy1zaG9lLXM3WGdTNC5qcGc=.imageset/YWltYWdlc2ZfYXV0byxiX3JnYjpmNWY1ZjUsd180NDBmc29sM2I3aGUxcnJkYmpsbTV4MGFpci1mb290c2NhcGUtbWlkLXV0aWxpdHktZG0tbWVucy1zaG9lLXM3WGdTNC5qcGc=.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc2ZfYXV0byxiX3JnYjpmNWY1ZjUsd180NDBmc29sM2I3aGUxcnJkYmpsbTV4MGFpci1mb290c2NhcGUtbWlkLXV0aWxpdHktZG0tbWVucy1zaG9lLXM3WGdTNC5qcGc=.imageset/YWltYWdlc2ZfYXV0byxiX3JnYjpmNWY1ZjUsd180NDBmc29sM2I3aGUxcnJkYmpsbTV4MGFpci1mb290c2NhcGUtbWlkLXV0aWxpdHktZG0tbWVucy1zaG9lLXM3WGdTNC5qcGc=.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc2ZfYXV0byxiX3JnYjpmNWY1ZjUsd180NDBuenlibnRkZDNhY3B3YW1xYXFycWNvcnRlei1iYXNpYy1sZWF0aGVyLW1lbnMtc2hvZS1SUFBDRzUuanBn.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "YWltYWdlc2ZfYXV0byxiX3JnYjpmNWY1ZjUsd180NDBuenlibnRkZDNhY3B3YW1xYXFycWNvcnRlei1iYXNpYy1sZWF0aGVyLW1lbnMtc2hvZS1SUFBDRzUuanBn.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvazF1OHJnMmZuemZwMjJqZTdwbnVuaWtlLWFpci1mb3JjZS0xLXByZW1pdW0tbjctcmVsZWFzZS1kYXRlLmpwZw==.imageset/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvazF1OHJnMmZuemZwMjJqZTdwbnVuaWtlLWFpci1mb3JjZS0xLXByZW1pdW0tbjctcmVsZWFzZS1kYXRlLmpwZw==.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvazF1OHJnMmZuemZwMjJqZTdwbnVuaWtlLWFpci1mb3JjZS0xLXByZW1pdW0tbjctcmVsZWFzZS1kYXRlLmpwZw==.imageset/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvazF1OHJnMmZuemZwMjJqZTdwbnVuaWtlLWFpci1mb3JjZS0xLXByZW1pdW0tbjctcmVsZWFzZS1kYXRlLmpwZw==.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvbnlieW95ampmYzhrbzVnZ3dianJ3b21lbnMtYWlyLWpvcmRhbi0zLWJvcmRlYXV4LXJlbGVhc2UtZGF0ZS5qcGc=.imageset/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvbnlieW95ampmYzhrbzVnZ3dianJ3b21lbnMtYWlyLWpvcmRhbi0zLWJvcmRlYXV4LXJlbGVhc2UtZGF0ZS5qcGc=.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvbnlieW95ampmYzhrbzVnZ3dianJ3b21lbnMtYWlyLWpvcmRhbi0zLWJvcmRlYXV4LXJlbGVhc2UtZGF0ZS5qcGc=.imageset/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvbnlieW95ampmYzhrbzVnZ3dianJ3b21lbnMtYWlyLWpvcmRhbi0zLWJvcmRlYXV4LXJlbGVhc2UtZGF0ZS5qcGc=.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/d3d3LnNuZWFrZXJlbGl0ZS5jb213cC1jb250ZW50dXBsb2FkczIwMTgwNmFkaWRhcy1ZZWV6eS1Cb29zdC0zNTAtVjItQnV0dGVyLVJlbGVhc2UtRGF0ZS1GMzY5ODAuanBn.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "d3d3LnNuZWFrZXJlbGl0ZS5jb213cC1jb250ZW50dXBsb2FkczIwMTgwNmFkaWRhcy1ZZWV6eS1Cb29zdC0zNTAtVjItQnV0dGVyLVJlbGVhc2UtRGF0ZS1GMzY5ODAuanBn.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc2ZfYXV0byxiX3JnYjpmNWY1ZjUsd180NDBmc29sM2I3aGUxcnJkYmpsbTV4MGFpci1mb290c2NhcGUtbWlkLXV0aWxpdHktZG0tbWVucy1zaG9lLXM3WGdTNC5qcGc=.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "YWltYWdlc2ZfYXV0byxiX3JnYjpmNWY1ZjUsd180NDBmc29sM2I3aGUxcnJkYmpsbTV4MGFpci1mb290c2NhcGUtbWlkLXV0aWxpdHktZG0tbWVucy1zaG9lLXM3WGdTNC5qcGc=.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvazF1OHJnMmZuemZwMjJqZTdwbnVuaWtlLWFpci1mb3JjZS0xLXByZW1pdW0tbjctcmVsZWFzZS1kYXRlLmpwZw==.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvazF1OHJnMmZuemZwMjJqZTdwbnVuaWtlLWFpci1mb3JjZS0xLXByZW1pdW0tbjctcmVsZWFzZS1kYXRlLmpwZw==.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvbnlieW95ampmYzhrbzVnZ3dianJ3b21lbnMtYWlyLWpvcmRhbi0zLWJvcmRlYXV4LXJlbGVhc2UtZGF0ZS5qcGc=.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvbnlieW95ampmYzhrbzVnZ3dianJ3b21lbnMtYWlyLWpvcmRhbi0zLWJvcmRlYXV4LXJlbGVhc2UtZGF0ZS5qcGc=.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvZjViM2E1amNxaWFycm5qZzR0aWJuaWtlLWFpci1mb3JjZS0xLXByZW1pdW0tanVzdC1kby1pdC1yZWxlYXNlLWRhdGUuanBn.imageset/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvZjViM2E1amNxaWFycm5qZzR0aWJuaWtlLWFpci1mb3JjZS0xLXByZW1pdW0tanVzdC1kby1pdC1yZWxlYXNlLWRhdGUuanBn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvZjViM2E1amNxaWFycm5qZzR0aWJuaWtlLWFpci1mb3JjZS0xLXByZW1pdW0tanVzdC1kby1pdC1yZWxlYXNlLWRhdGUuanBn.imageset/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvZjViM2E1amNxaWFycm5qZzR0aWJuaWtlLWFpci1mb3JjZS0xLXByZW1pdW0tanVzdC1kby1pdC1yZWxlYXNlLWRhdGUuanBn.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvcDl0cnV4eHB2M3Bkb2U3M3hobG9haXItam9yZGFuLTYtcmV0cm8tdGlua2VyLWluZnJhcmVkLXJlbGVhc2UtZGF0ZS5qcGc=.imageset/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvcDl0cnV4eHB2M3Bkb2U3M3hobG9haXItam9yZGFuLTYtcmV0cm8tdGlua2VyLWluZnJhcmVkLXJlbGVhc2UtZGF0ZS5qcGc=.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvcDl0cnV4eHB2M3Bkb2U3M3hobG9haXItam9yZGFuLTYtcmV0cm8tdGlua2VyLWluZnJhcmVkLXJlbGVhc2UtZGF0ZS5qcGc=.imageset/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvcDl0cnV4eHB2M3Bkb2U3M3hobG9haXItam9yZGFuLTYtcmV0cm8tdGlua2VyLWluZnJhcmVkLXJlbGVhc2UtZGF0ZS5qcGc=.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRveTU0ejh3bnc0ZWVyMXR4MWFrdmluaWtlLWFpci1tYXgtOTctYmxhY2stbWV0YWxsaWMtZ29sZC1yZWxlYXNlLWRhdGUuanBn.imageset/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRveTU0ejh3bnc0ZWVyMXR4MWFrdmluaWtlLWFpci1tYXgtOTctYmxhY2stbWV0YWxsaWMtZ29sZC1yZWxlYXNlLWRhdGUuanBn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRveTU0ejh3bnc0ZWVyMXR4MWFrdmluaWtlLWFpci1tYXgtOTctYmxhY2stbWV0YWxsaWMtZ29sZC1yZWxlYXNlLWRhdGUuanBn.imageset/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRveTU0ejh3bnc0ZWVyMXR4MWFrdmluaWtlLWFpci1tYXgtOTctYmxhY2stbWV0YWxsaWMtZ29sZC1yZWxlYXNlLWRhdGUuanBn.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvZjViM2E1amNxaWFycm5qZzR0aWJuaWtlLWFpci1mb3JjZS0xLXByZW1pdW0tanVzdC1kby1pdC1yZWxlYXNlLWRhdGUuanBn.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvZjViM2E1amNxaWFycm5qZzR0aWJuaWtlLWFpci1mb3JjZS0xLXByZW1pdW0tanVzdC1kby1pdC1yZWxlYXNlLWRhdGUuanBn.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvcDl0cnV4eHB2M3Bkb2U3M3hobG9haXItam9yZGFuLTYtcmV0cm8tdGlua2VyLWluZnJhcmVkLXJlbGVhc2UtZGF0ZS5qcGc=.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvcDl0cnV4eHB2M3Bkb2U3M3hobG9haXItam9yZGFuLTYtcmV0cm8tdGlua2VyLWluZnJhcmVkLXJlbGVhc2UtZGF0ZS5qcGc=.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRveTU0ejh3bnc0ZWVyMXR4MWFrdmluaWtlLWFpci1tYXgtOTctYmxhY2stbWV0YWxsaWMtZ29sZC1yZWxlYXNlLWRhdGUuanBn.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRveTU0ejh3bnc0ZWVyMXR4MWFrdmluaWtlLWFpci1tYXgtOTctYmxhY2stbWV0YWxsaWMtZ29sZC1yZWxlYXNlLWRhdGUuanBn.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvYXd4YXh5N2l0cDA0eWx5ZGlwY2FuaWtlLWFpci1tYXgtOTUtbnJnLWJsYWNrLWFudGhyYWNpdGUtcmVsZWFzZS1kYXRlLmpwZw==.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvYXd4YXh5N2l0cDA0eWx5ZGlwY2FuaWtlLWFpci1tYXgtOTUtbnJnLWJsYWNrLWFudGhyYWNpdGUtcmVsZWFzZS1kYXRlLmpwZw==.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvYXd4YXh5N2l0cDA0eWx5ZGlwY2FuaWtlLWFpci1tYXgtOTUtbnJnLWJsYWNrLWFudGhyYWNpdGUtcmVsZWFzZS1kYXRlLmpwZw==.imageset/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvYXd4YXh5N2l0cDA0eWx5ZGlwY2FuaWtlLWFpci1tYXgtOTUtbnJnLWJsYWNrLWFudGhyYWNpdGUtcmVsZWFzZS1kYXRlLmpwZw==.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvYXd4YXh5N2l0cDA0eWx5ZGlwY2FuaWtlLWFpci1tYXgtOTUtbnJnLWJsYWNrLWFudGhyYWNpdGUtcmVsZWFzZS1kYXRlLmpwZw==.imageset/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvYXd4YXh5N2l0cDA0eWx5ZGlwY2FuaWtlLWFpci1tYXgtOTUtbnJnLWJsYWNrLWFudGhyYWNpdGUtcmVsZWFzZS1kYXRlLmpwZw==.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvd29rOG0zcHgwMTF2ZWptNzJzeWRuaWtlLWFpci1tYXgtOTctbWV0YWxsaWMtc2lsdmVyLWJsYWNrLXJlbGVhc2UtZGF0ZS5qcGc=.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvd29rOG0zcHgwMTF2ZWptNzJzeWRuaWtlLWFpci1tYXgtOTctbWV0YWxsaWMtc2lsdmVyLWJsYWNrLXJlbGVhc2UtZGF0ZS5qcGc=.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvd29rOG0zcHgwMTF2ZWptNzJzeWRuaWtlLWFpci1tYXgtOTctbWV0YWxsaWMtc2lsdmVyLWJsYWNrLXJlbGVhc2UtZGF0ZS5qcGc=.imageset/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvd29rOG0zcHgwMTF2ZWptNzJzeWRuaWtlLWFpci1tYXgtOTctbWV0YWxsaWMtc2lsdmVyLWJsYWNrLXJlbGVhc2UtZGF0ZS5qcGc=.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvd29rOG0zcHgwMTF2ZWptNzJzeWRuaWtlLWFpci1tYXgtOTctbWV0YWxsaWMtc2lsdmVyLWJsYWNrLXJlbGVhc2UtZGF0ZS5qcGc=.imageset/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvd29rOG0zcHgwMTF2ZWptNzJzeWRuaWtlLWFpci1tYXgtOTctbWV0YWxsaWMtc2lsdmVyLWJsYWNrLXJlbGVhc2UtZGF0ZS5qcGc=.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvd2oxYXJ3Z21xazB4MHJqbm54aDduaWtlLWFpci1tYXgtOTctbWV0YWxsaWMtc2lsdmVyLXdoaXRlLXJlbGVhc2UtZGF0ZS5qcGc=.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvd2oxYXJ3Z21xazB4MHJqbm54aDduaWtlLWFpci1tYXgtOTctbWV0YWxsaWMtc2lsdmVyLXdoaXRlLXJlbGVhc2UtZGF0ZS5qcGc=.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvd2oxYXJ3Z21xazB4MHJqbm54aDduaWtlLWFpci1tYXgtOTctbWV0YWxsaWMtc2lsdmVyLXdoaXRlLXJlbGVhc2UtZGF0ZS5qcGc=.imageset/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvd2oxYXJ3Z21xazB4MHJqbm54aDduaWtlLWFpci1tYXgtOTctbWV0YWxsaWMtc2lsdmVyLXdoaXRlLXJlbGVhc2UtZGF0ZS5qcGc=.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvd2oxYXJ3Z21xazB4MHJqbm54aDduaWtlLWFpci1tYXgtOTctbWV0YWxsaWMtc2lsdmVyLXdoaXRlLXJlbGVhc2UtZGF0ZS5qcGc=.imageset/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvd2oxYXJ3Z21xazB4MHJqbm54aDduaWtlLWFpci1tYXgtOTctbWV0YWxsaWMtc2lsdmVyLXdoaXRlLXJlbGVhc2UtZGF0ZS5qcGc=.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvanF3eW1laGJ5c21ueG1hd3JyNmJ3b21lbnMtYWlyLWZvcmNlLTEtc2FnZS1oaWdoLXdoaXRlLWJsYWNrLXJlbGVhc2UtZGF0ZS5qcGc=.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvanF3eW1laGJ5c21ueG1hd3JyNmJ3b21lbnMtYWlyLWZvcmNlLTEtc2FnZS1oaWdoLXdoaXRlLWJsYWNrLXJlbGVhc2UtZGF0ZS5qcGc=.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvbWJpaDc5Z3htbG1veTFhbmx5cHZ3b21lbnMtYWYxLWplc3Rlci1oaS14eC1ibGFjay1kdXN0eS1wZWFjaC1yZWxlYXNlLWRhdGUuanBn.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvbWJpaDc5Z3htbG1veTFhbmx5cHZ3b21lbnMtYWYxLWplc3Rlci1oaS14eC1ibGFjay1kdXN0eS1wZWFjaC1yZWxlYXNlLWRhdGUuanBn.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvanF3eW1laGJ5c21ueG1hd3JyNmJ3b21lbnMtYWlyLWZvcmNlLTEtc2FnZS1oaWdoLXdoaXRlLWJsYWNrLXJlbGVhc2UtZGF0ZS5qcGc=.imageset/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvanF3eW1laGJ5c21ueG1hd3JyNmJ3b21lbnMtYWlyLWZvcmNlLTEtc2FnZS1oaWdoLXdoaXRlLWJsYWNrLXJlbGVhc2UtZGF0ZS5qcGc=.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvanF3eW1laGJ5c21ueG1hd3JyNmJ3b21lbnMtYWlyLWZvcmNlLTEtc2FnZS1oaWdoLXdoaXRlLWJsYWNrLXJlbGVhc2UtZGF0ZS5qcGc=.imageset/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvanF3eW1laGJ5c21ueG1hd3JyNmJ3b21lbnMtYWlyLWZvcmNlLTEtc2FnZS1oaWdoLXdoaXRlLWJsYWNrLXJlbGVhc2UtZGF0ZS5qcGc=.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvbWJpaDc5Z3htbG1veTFhbmx5cHZ3b21lbnMtYWYxLWplc3Rlci1oaS14eC1ibGFjay1kdXN0eS1wZWFjaC1yZWxlYXNlLWRhdGUuanBn.imageset/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvbWJpaDc5Z3htbG1veTFhbmx5cHZ3b21lbnMtYWYxLWplc3Rlci1oaS14eC1ibGFjay1kdXN0eS1wZWFjaC1yZWxlYXNlLWRhdGUuanBn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvbWJpaDc5Z3htbG1veTFhbmx5cHZ3b21lbnMtYWYxLWplc3Rlci1oaS14eC1ibGFjay1kdXN0eS1wZWFjaC1yZWxlYXNlLWRhdGUuanBn.imageset/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvbWJpaDc5Z3htbG1veTFhbmx5cHZ3b21lbnMtYWYxLWplc3Rlci1oaS14eC1ibGFjay1kdXN0eS1wZWFjaC1yZWxlYXNlLWRhdGUuanBn.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvc2Jsc3F6aWthcHFnemk3OXl5cXNuaWtlLWFpci1tYXgtOTctbnJnLXRlYW0tcmVkLW1pZG5pZ2h0LXNwcnVjZS1yZWxlYXNlLWRhdGUuanBn.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvc2Jsc3F6aWthcHFnemk3OXl5cXNuaWtlLWFpci1tYXgtOTctbnJnLXRlYW0tcmVkLW1pZG5pZ2h0LXNwcnVjZS1yZWxlYXNlLWRhdGUuanBn.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvcXh1MDBpaHh5dDVyZjBzZ2ZyYnVuaWtlLWFpci1tYXgtOTUtcHJlbWl1bS12b2x0LXZvbHQtZ2xvdy1ibGFjay1yZWxlYXNlLWRhdGUuanBn.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvcXh1MDBpaHh5dDVyZjBzZ2ZyYnVuaWtlLWFpci1tYXgtOTUtcHJlbWl1bS12b2x0LXZvbHQtZ2xvdy1ibGFjay1yZWxlYXNlLWRhdGUuanBn.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/DomainLayer/UseCases/FetchShoesUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FetchShoesUseCase.swift 3 | // MVP-Sample 4 | // 5 | // Created by NixonShih on 2019/4/11. 6 | // Copyright © 2019 NixonShih. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct FetchShoesUseCase: FetchDataUseCaseSpec { 12 | 13 | typealias DataModel = [ShoesModel] 14 | 15 | init(repository: ShoesRepositorySpec) { 16 | self.repository = repository 17 | } 18 | 19 | func fetchDataModel(_ completionHandler: @escaping FetchDataModelUseCaseCompletionHandler) { 20 | repository.fetchShoes(completionHandler) 21 | } 22 | 23 | // MARK: private 24 | 25 | private let repository: ShoesRepositorySpec 26 | } 27 | -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvdTVkOGNscms4dGN3anR1aXlsYnZuaWtlLXdvbWVucy1haXItZm9yY2UtMS1yZWJlbC14eC1wcmFsaW5lLXdoaXRlLXJlbGVhc2UtZGF0ZS5qcGc=.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvdTVkOGNscms4dGN3anR1aXlsYnZuaWtlLXdvbWVucy1haXItZm9yY2UtMS1yZWJlbC14eC1wcmFsaW5lLXdoaXRlLXJlbGVhc2UtZGF0ZS5qcGc=.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvdWNycWRwMXdiYnh2cWp5MDdmcmV3b21lbnMtYWYxLWplc3Rlci1oaS14eC12aW9sZXQtYXNoLWJsdWUtZm9yY2UtcmVsZWFzZS1kYXRlLmpwZw==.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvdWNycWRwMXdiYnh2cWp5MDdmcmV3b21lbnMtYWYxLWplc3Rlci1oaS14eC12aW9sZXQtYXNoLWJsdWUtZm9yY2UtcmVsZWFzZS1kYXRlLmpwZw==.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRveXFhY3F2cWNqaHFwYmduZ3VheWVhaXItam9yZGFuLTEyLWludGVybmF0aW9uYWwtZmxpZ2h0LWNvbGxlZ2UtbmF2eS1yZWxlYXNlLWRhdGUuanBn.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRveXFhY3F2cWNqaHFwYmduZ3VheWVhaXItam9yZGFuLTEyLWludGVybmF0aW9uYWwtZmxpZ2h0LWNvbGxlZ2UtbmF2eS1yZWxlYXNlLWRhdGUuanBn.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvc2Jsc3F6aWthcHFnemk3OXl5cXNuaWtlLWFpci1tYXgtOTctbnJnLXRlYW0tcmVkLW1pZG5pZ2h0LXNwcnVjZS1yZWxlYXNlLWRhdGUuanBn.imageset/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvc2Jsc3F6aWthcHFnemk3OXl5cXNuaWtlLWFpci1tYXgtOTctbnJnLXRlYW0tcmVkLW1pZG5pZ2h0LXNwcnVjZS1yZWxlYXNlLWRhdGUuanBn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvc2Jsc3F6aWthcHFnemk3OXl5cXNuaWtlLWFpci1tYXgtOTctbnJnLXRlYW0tcmVkLW1pZG5pZ2h0LXNwcnVjZS1yZWxlYXNlLWRhdGUuanBn.imageset/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvc2Jsc3F6aWthcHFnemk3OXl5cXNuaWtlLWFpci1tYXgtOTctbnJnLXRlYW0tcmVkLW1pZG5pZ2h0LXNwcnVjZS1yZWxlYXNlLWRhdGUuanBn.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvcThpMXJvZGd4OWNjaXI1Ymdzc2VuaWtlLWFpci1mb3JjZS0xLWx2OC11dGlsaXR5LXZvbHQtd29sZi1ncmV5LXdoaXRlLXJlbGVhc2UtZGF0ZS5qcGc=.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvcThpMXJvZGd4OWNjaXI1Ymdzc2VuaWtlLWFpci1mb3JjZS0xLWx2OC11dGlsaXR5LXZvbHQtd29sZi1ncmV5LXdoaXRlLXJlbGVhc2UtZGF0ZS5qcGc=.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvcXh1MDBpaHh5dDVyZjBzZ2ZyYnVuaWtlLWFpci1tYXgtOTUtcHJlbWl1bS12b2x0LXZvbHQtZ2xvdy1ibGFjay1yZWxlYXNlLWRhdGUuanBn.imageset/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvcXh1MDBpaHh5dDVyZjBzZ2ZyYnVuaWtlLWFpci1tYXgtOTUtcHJlbWl1bS12b2x0LXZvbHQtZ2xvdy1ibGFjay1yZWxlYXNlLWRhdGUuanBn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvcXh1MDBpaHh5dDVyZjBzZ2ZyYnVuaWtlLWFpci1tYXgtOTUtcHJlbWl1bS12b2x0LXZvbHQtZ2xvdy1ibGFjay1yZWxlYXNlLWRhdGUuanBn.imageset/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvcXh1MDBpaHh5dDVyZjBzZ2ZyYnVuaWtlLWFpci1tYXgtOTUtcHJlbWl1bS12b2x0LXZvbHQtZ2xvdy1ibGFjay1yZWxlYXNlLWRhdGUuanBn.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvdmd4NXR6cmd2YTBpcmxzYnJjcGluaWtlLXNiLWxvdy1wcm8tZGlhbW9uZC1ibGFjay10cm9waWNhbC10d2lzdC1jaHJvbWUtcmVsZWFzZS1kYXRlLmpwZw==.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvdmd4NXR6cmd2YTBpcmxzYnJjcGluaWtlLXNiLWxvdy1wcm8tZGlhbW9uZC1ibGFjay10cm9waWNhbC10d2lzdC1jaHJvbWUtcmVsZWFzZS1kYXRlLmpwZw==.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvdTVkOGNscms4dGN3anR1aXlsYnZuaWtlLXdvbWVucy1haXItZm9yY2UtMS1yZWJlbC14eC1wcmFsaW5lLXdoaXRlLXJlbGVhc2UtZGF0ZS5qcGc=.imageset/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvdTVkOGNscms4dGN3anR1aXlsYnZuaWtlLXdvbWVucy1haXItZm9yY2UtMS1yZWJlbC14eC1wcmFsaW5lLXdoaXRlLXJlbGVhc2UtZGF0ZS5qcGc=.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvdTVkOGNscms4dGN3anR1aXlsYnZuaWtlLXdvbWVucy1haXItZm9yY2UtMS1yZWJlbC14eC1wcmFsaW5lLXdoaXRlLXJlbGVhc2UtZGF0ZS5qcGc=.imageset/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvdTVkOGNscms4dGN3anR1aXlsYnZuaWtlLXdvbWVucy1haXItZm9yY2UtMS1yZWJlbC14eC1wcmFsaW5lLXdoaXRlLXJlbGVhc2UtZGF0ZS5qcGc=.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvdWNycWRwMXdiYnh2cWp5MDdmcmV3b21lbnMtYWYxLWplc3Rlci1oaS14eC12aW9sZXQtYXNoLWJsdWUtZm9yY2UtcmVsZWFzZS1kYXRlLmpwZw==.imageset/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvdWNycWRwMXdiYnh2cWp5MDdmcmV3b21lbnMtYWYxLWplc3Rlci1oaS14eC12aW9sZXQtYXNoLWJsdWUtZm9yY2UtcmVsZWFzZS1kYXRlLmpwZw==.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvdWNycWRwMXdiYnh2cWp5MDdmcmV3b21lbnMtYWYxLWplc3Rlci1oaS14eC12aW9sZXQtYXNoLWJsdWUtZm9yY2UtcmVsZWFzZS1kYXRlLmpwZw==.imageset/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvdWNycWRwMXdiYnh2cWp5MDdmcmV3b21lbnMtYWYxLWplc3Rlci1oaS14eC12aW9sZXQtYXNoLWJsdWUtZm9yY2UtcmVsZWFzZS1kYXRlLmpwZw==.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRveXFhY3F2cWNqaHFwYmduZ3VheWVhaXItam9yZGFuLTEyLWludGVybmF0aW9uYWwtZmxpZ2h0LWNvbGxlZ2UtbmF2eS1yZWxlYXNlLWRhdGUuanBn.imageset/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRveXFhY3F2cWNqaHFwYmduZ3VheWVhaXItam9yZGFuLTEyLWludGVybmF0aW9uYWwtZmxpZ2h0LWNvbGxlZ2UtbmF2eS1yZWxlYXNlLWRhdGUuanBn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRveXFhY3F2cWNqaHFwYmduZ3VheWVhaXItam9yZGFuLTEyLWludGVybmF0aW9uYWwtZmxpZ2h0LWNvbGxlZ2UtbmF2eS1yZWxlYXNlLWRhdGUuanBn.imageset/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRveXFhY3F2cWNqaHFwYmduZ3VheWVhaXItam9yZGFuLTEyLWludGVybmF0aW9uYWwtZmxpZ2h0LWNvbGxlZ2UtbmF2eS1yZWxlYXNlLWRhdGUuanBn.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvd3drY29xcHY5dnh5aDRwZ3dkZ25uaWtlLWFpci1mb3JjZS0xLXByZW1pdW0tanVzdC1kby1pdC1jb2xsZWN0aW9uLXRvdGFsLW9yYW5nZS1yZWxlYXNlLWRhdGUuanBn.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvd3drY29xcHY5dnh5aDRwZ3dkZ25uaWtlLWFpci1mb3JjZS0xLXByZW1pdW0tanVzdC1kby1pdC1jb2xsZWN0aW9uLXRvdGFsLW9yYW5nZS1yZWxlYXNlLWRhdGUuanBn.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvcThpMXJvZGd4OWNjaXI1Ymdzc2VuaWtlLWFpci1mb3JjZS0xLWx2OC11dGlsaXR5LXZvbHQtd29sZi1ncmV5LXdoaXRlLXJlbGVhc2UtZGF0ZS5qcGc=.imageset/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvcThpMXJvZGd4OWNjaXI1Ymdzc2VuaWtlLWFpci1mb3JjZS0xLWx2OC11dGlsaXR5LXZvbHQtd29sZi1ncmV5LXdoaXRlLXJlbGVhc2UtZGF0ZS5qcGc=.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvcThpMXJvZGd4OWNjaXI1Ymdzc2VuaWtlLWFpci1mb3JjZS0xLWx2OC11dGlsaXR5LXZvbHQtd29sZi1ncmV5LXdoaXRlLXJlbGVhc2UtZGF0ZS5qcGc=.imageset/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvcThpMXJvZGd4OWNjaXI1Ymdzc2VuaWtlLWFpci1mb3JjZS0xLWx2OC11dGlsaXR5LXZvbHQtd29sZi1ncmV5LXdoaXRlLXJlbGVhc2UtZGF0ZS5qcGc=.jpg -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/compleximagesfl_lossy,q_autoc_crop,h_502,w_856,x_0,y_183c_scale,w_690,dpr_2.0v1ctxstklbxucwkotuhh72air-jordan-1-shattered-backboard-3-0-black-pale-vanilla-starfish-555088-028-rendering.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "compleximagesfl_lossy,q_autoc_crop,h_502,w_856,x_0,y_183c_scale,w_690,dpr_2.0v1ctxstklbxucwkotuhh72air-jordan-1-shattered-backboard-3-0-black-pale-vanilla-starfish-555088-028-rendering.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvdmd4NXR6cmd2YTBpcmxzYnJjcGluaWtlLXNiLWxvdy1wcm8tZGlhbW9uZC1ibGFjay10cm9waWNhbC10d2lzdC1jaHJvbWUtcmVsZWFzZS1kYXRlLmpwZw==.imageset/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvdmd4NXR6cmd2YTBpcmxzYnJjcGluaWtlLXNiLWxvdy1wcm8tZGlhbW9uZC1ibGFjay10cm9waWNhbC10d2lzdC1jaHJvbWUtcmVsZWFzZS1kYXRlLmpwZw==.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvdmd4NXR6cmd2YTBpcmxzYnJjcGluaWtlLXNiLWxvdy1wcm8tZGlhbW9uZC1ibGFjay10cm9waWNhbC10d2lzdC1jaHJvbWUtcmVsZWFzZS1kYXRlLmpwZw==.imageset/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvdmd4NXR6cmd2YTBpcmxzYnJjcGluaWtlLXNiLWxvdy1wcm8tZGlhbW9uZC1ibGFjay10cm9waWNhbC10d2lzdC1jaHJvbWUtcmVsZWFzZS1kYXRlLmpwZw==.jpg -------------------------------------------------------------------------------- /MVP-SampleTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /MVP-SampleTests/Helper/Model/ShoesModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShoesModel.swift 3 | // MVP-SampleTests 4 | // 5 | // Created by NixonShih on 2019/4/16. 6 | // Copyright © 2019 NixonShih. All rights reserved. 7 | // 8 | 9 | @testable import MVP_Sample 10 | 11 | extension ShoesModel { 12 | 13 | static var newShoes: ShoesModel { 14 | return ShoesModel(date: "2018-06-28", description: "123", imageName: "456", nickname: "Just Do It Collection", price: 130, title: "Nike Air Force 1 ’07 PRM") 15 | } 16 | } 17 | 18 | extension ShoesModel: Equatable { 19 | 20 | public static func == (lhs: ShoesModel, rhs: ShoesModel) -> Bool { 21 | return lhs.date + lhs.title + String(lhs.price) + lhs.imageName == 22 | rhs.date + rhs.title + String(rhs.price) + rhs.imageName 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /MVP-Sample/ViewLayer/BaseClass/BaseViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseViewController.swift 3 | // MVP-Sample 4 | // 5 | // Created by Nixon.Shih on 2018/11/9. 6 | // Copyright © 2018 NixonShih. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class BaseViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | view.backgroundColor = ThemeColor.darkBlack.color 16 | navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil) 17 | } 18 | 19 | override func viewWillAppear(_ animated: Bool) { 20 | super.viewWillAppear(animated) 21 | } 22 | 23 | override var preferredStatusBarStyle: UIStatusBarStyle { 24 | return UIStatusBarStyle.lightContent 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvd3drY29xcHY5dnh5aDRwZ3dkZ25uaWtlLWFpci1mb3JjZS0xLXByZW1pdW0tanVzdC1kby1pdC1jb2xsZWN0aW9uLXRvdGFsLW9yYW5nZS1yZWxlYXNlLWRhdGUuanBn.imageset/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvd3drY29xcHY5dnh5aDRwZ3dkZ25uaWtlLWFpci1mb3JjZS0xLXByZW1pdW0tanVzdC1kby1pdC1jb2xsZWN0aW9uLXRvdGFsLW9yYW5nZS1yZWxlYXNlLWRhdGUuanBn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvd3drY29xcHY5dnh5aDRwZ3dkZ25uaWtlLWFpci1mb3JjZS0xLXByZW1pdW0tanVzdC1kby1pdC1jb2xsZWN0aW9uLXRvdGFsLW9yYW5nZS1yZWxlYXNlLWRhdGUuanBn.imageset/YWltYWdlc3RfcHJvZF9zc3dfOTYwLGNfbGltaXQsZl9hdXRvd3drY29xcHY5dnh5aDRwZ3dkZ25uaWtlLWFpci1mb3JjZS0xLXByZW1pdW0tanVzdC1kby1pdC1jb2xsZWN0aW9uLXRvdGFsLW9yYW5nZS1yZWxlYXNlLWRhdGUuanBn.jpg -------------------------------------------------------------------------------- /MVP-Sample/ViewLayer/Scenes/ShoesList/ShoesListRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShoesListRouter.swift 3 | // MVP-Sample 4 | // 5 | // Created by NixonShih on 2019/4/13. 6 | // Copyright © 2019 NixonShih. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol ShoesListRouterSpec { 12 | func pushDetailView(with shoes: ShoesModel) 13 | } 14 | 15 | struct ShoesListRouter: ShoesListRouterSpec { 16 | 17 | init(performer: ShoesListViewController) { 18 | self.performer = performer 19 | } 20 | 21 | func pushDetailView(with shoes: ShoesModel) { 22 | let vc = ShoesDetailViewBuilder(shoes: shoes).build() 23 | performer.navigationController?.pushViewController(vc, animated: true) 24 | } 25 | 26 | // MARK: private 27 | 28 | private weak var performer: ShoesListViewController! 29 | } 30 | -------------------------------------------------------------------------------- /MVP-Sample/ViewLayer/Scenes/ShoesDetail/ShoesDetailViewBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShoesDetailViewBuilder.swift 3 | // MVP-Sample 4 | // 5 | // Created by NixonShih on 2019/4/13. 6 | // Copyright © 2019 NixonShih. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// The builder of ShoesDetailViewController 12 | struct ShoesDetailViewBuilder: ViewBuilderSpec { 13 | 14 | init(shoes: ShoesModel) { 15 | self.shoes = shoes 16 | } 17 | 18 | func build() -> ShoesDetailViewController { 19 | let vc = UIStoryboard.main.instantiateViewController(withClass: ShoesDetailViewController.self) 20 | vc.presenter = ShoesDetailPresenter(shoes: shoes) 21 | vc.presenter.eventReceiver = vc 22 | return vc 23 | } 24 | 25 | // MARK: private 26 | 27 | private let shoes: ShoesModel 28 | } 29 | -------------------------------------------------------------------------------- /MVP-Sample/Assets.xcassets/Shoes/compleximagesfl_lossy,q_autoc_crop,h_502,w_856,x_0,y_183c_scale,w_690,dpr_2.0v1ctxstklbxucwkotuhh72air-jordan-1-shattered-backboard-3-0-black-pale-vanilla-starfish-555088-028-rendering.imageset/compleximagesfl_lossy,q_autoc_crop,h_502,w_856,x_0,y_183c_scale,w_690,dpr_2.0v1ctxstklbxucwkotuhh72air-jordan-1-shattered-backboard-3-0-black-pale-vanilla-starfish-555088-028-rendering.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerwolf543/Swift-MVP-Sample/HEAD/MVP-Sample/Assets.xcassets/Shoes/compleximagesfl_lossy,q_autoc_crop,h_502,w_856,x_0,y_183c_scale,w_690,dpr_2.0v1ctxstklbxucwkotuhh72air-jordan-1-shattered-backboard-3-0-black-pale-vanilla-starfish-555088-028-rendering.imageset/compleximagesfl_lossy,q_autoc_crop,h_502,w_856,x_0,y_183c_scale,w_690,dpr_2.0v1ctxstklbxucwkotuhh72air-jordan-1-shattered-backboard-3-0-black-pale-vanilla-starfish-555088-028-rendering.jpg -------------------------------------------------------------------------------- /MVP-Sample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // MVP-Sample 4 | // 5 | // Created by NixonShih on 2018/10/29. 6 | // Copyright © 2018 NixonShih. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | var window: UIWindow? 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { 16 | LocalShoesRepository.removeAll() 17 | setupFirstView() 18 | return true 19 | } 20 | 21 | // MARK: private 22 | 23 | func setupFirstView () { 24 | window = UIWindow() 25 | window?.rootViewController = ShoesListViewBuilder().buildWithNavigationController() 26 | window?.makeKeyAndVisible() 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /MVP-Sample/ViewLayer/Scenes/ShoesList/Banner/ShoesListBannerRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShoesListBannerRouter.swift 3 | // MVP-Sample 4 | // 5 | // Created by NixonShih on 2019/4/15. 6 | // Copyright © 2019 NixonShih. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SafariServices 11 | 12 | protocol ShoesListBannerRouterSpec { 13 | func pushWebView(with url: URL) 14 | } 15 | 16 | struct ShoesListBannerRouter: ShoesListBannerRouterSpec { 17 | 18 | init(performer: ShoesListBannerViewController) { 19 | self.performer = performer 20 | } 21 | 22 | func pushWebView(with url: URL) { 23 | let vc = SFSafariViewController(url: url) 24 | performer.navigationController?.pushViewController(vc, animated: true) 25 | } 26 | 27 | // MARK: private 28 | 29 | private weak var performer: ShoesListBannerViewController! 30 | } 31 | -------------------------------------------------------------------------------- /MVP-Sample/Other/Extension/UIColor+extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+extensions.swift 3 | // MVP-Sample 4 | // 5 | // Created by Nixon.Shih on 2018/11/9. 6 | // Copyright © 2018 NixonShih. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIColor { 12 | 13 | convenience init(red: Int, green: Int, blue: Int) { 14 | assert(red >= 0 && red <= 255, "Invalid red component") 15 | assert(green >= 0 && green <= 255, "Invalid green component") 16 | assert(blue >= 0 && blue <= 255, "Invalid blue component") 17 | 18 | self.init(red: CGFloat(red) / 255.0, green: CGFloat(green) / 255.0, blue: CGFloat(blue) / 255.0, alpha: 1.0) 19 | } 20 | 21 | convenience init(rgb: Int) { 22 | self.init( 23 | red: (rgb >> 16) & 0xFF, 24 | green: (rgb >> 8) & 0xFF, 25 | blue: rgb & 0xFF 26 | ) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /MVP-Sample/Other/Util/ScrollViewCalculator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScrollViewCalculator.swift 3 | // MVP-Sample 4 | // 5 | // Created by Nixon.Shih on 2019/4/15. 6 | // Copyright © 2019 NixonShih. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ScrollViewCalculator { 12 | 13 | enum Direction { 14 | case up 15 | case down 16 | } 17 | 18 | init(scrollView: UIScrollView, originOffset: CGFloat = 0) { 19 | self.scrollView = scrollView 20 | previousScrollOffset = originOffset 21 | } 22 | 23 | var scrollDifference: CGFloat { 24 | return scrollView.contentOffset.y - previousScrollOffset 25 | } 26 | 27 | var moveDirection: Direction { 28 | return scrollDifference > 0 ? .up : .down 29 | } 30 | 31 | var previousScrollOffset: CGFloat 32 | 33 | // MARK: private 34 | 35 | private let scrollView: UIScrollView 36 | } 37 | -------------------------------------------------------------------------------- /MVP-Sample/SupportFiles/Dummy/banner_remote_source.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 0, 3 | "msg": "success", 4 | "body": [ 5 | { 6 | "imageName": "banner1", 7 | "url": "https://github.com/powerwolf543" 8 | }, 9 | { 10 | "imageName": "banner2", 11 | "url": "https://github.com/powerwolf543/Swift-MVP-Sample" 12 | }, 13 | { 14 | "imageName": "banner3", 15 | "url": "https://github.com/powerwolf543" 16 | }, 17 | { 18 | "imageName": "banner4", 19 | "url": "https://github.com/powerwolf543/Swift-MVP-Sample" 20 | }, 21 | { 22 | "imageName": "banner5", 23 | "url": "https://github.com/powerwolf543" 24 | }, 25 | { 26 | "imageName": "banner6", 27 | "url": "https://github.com/powerwolf543/Swift-MVP-Sample" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /MVP-Sample/ViewLayer/Scenes/ShoesDetail/ShoesDetailDescriptionTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShoesDetailDescriptionTableViewCell.swift 3 | // MVP-Sample 4 | // 5 | // Created by NixonShih on 2019/4/13. 6 | // Copyright © 2019 NixonShih. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct ShoesDetailDescriptionCellModel { 12 | let description: String 13 | } 14 | 15 | class ShoesDetailDescriptionTableViewCell: UITableViewCell, SetReuseViewModelable { 16 | 17 | @IBOutlet weak var descriptionLabel: UILabel! 18 | 19 | func setReuseViewMode(_ viewModel: ShoesDetailDescriptionCellModel) { 20 | descriptionLabel.text = viewModel.description 21 | } 22 | 23 | required init?(coder aDecoder: NSCoder) { 24 | super.init(coder: aDecoder) 25 | setup() 26 | } 27 | 28 | // MARK: private 29 | 30 | private func setup() { 31 | selectionStyle = .none 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /MVP-Sample/Other/Extension/UIStoryboard+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIStoryboard+Extensions.swift 3 | // MVP-Sample 4 | // 5 | // Created by Nixon.Shih on 2019/4/12. 6 | // Copyright © 2019 NixonShih. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIStoryboard { 12 | 13 | static var main: UIStoryboard { 14 | return UIStoryboard(name: "Main", bundle: Bundle.main) 15 | } 16 | 17 | /// Instantiate a UIViewController using its class name 18 | /// 19 | /// - Parameter name: UIViewController type 20 | /// - Returns: The view controller corresponding to specified class name 21 | func instantiateViewController(withClass name: T.Type) -> T { 22 | guard let viewController = instantiateViewController(withIdentifier: String(describing: name)) as? T else { 23 | fatalError("storyboard identifier not found.") 24 | } 25 | return viewController 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /MVP-Sample/ViewLayer/BaseClass/BaseNavigationController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseNavigationController.swift 3 | // MVP-Sample 4 | // 5 | // Created by Nixon.Shih on 2018/11/9. 6 | // Copyright © 2018 NixonShih. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class BaseNavigationController: UINavigationController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | 16 | updatedTheme() 17 | interactivePopGestureRecognizer?.isEnabled = true 18 | } 19 | 20 | override var preferredStatusBarStyle: UIStatusBarStyle { 21 | return UIStatusBarStyle.lightContent 22 | } 23 | 24 | private func updatedTheme() { 25 | 26 | navigationBar.barStyle = .blackOpaque 27 | navigationBar.isTranslucent = false 28 | navigationBar.barTintColor = ThemeColor.pureBlack.color 29 | navigationBar.tintColor = ThemeColor.pureWhite.color 30 | navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /MVP-Sample/ViewLayer/Scenes/ShoesList/Banner/ShoesListBannerBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShoesListBannerBuilder.swift 3 | // MVP-Sample 4 | // 5 | // Created by NixonShih on 2019/4/14. 6 | // Copyright © 2019 NixonShih. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// The builder of ShoesListBannerViewController 12 | struct ShoesListBannerBuilder: ViewBuilderSpec { 13 | 14 | /// builds ViewController and injects dependency of components. 15 | func build() -> ShoesListBannerViewController { 16 | 17 | let fetcher = BannerFetcher() 18 | let repository = RemoteBannerRepository(fetcher: fetcher) 19 | let useCase = FetchBannerUseCase(repository: repository) 20 | let vc = UIStoryboard.main.instantiateViewController(withClass: ShoesListBannerViewController.self) 21 | let presenter = ShoesListBannerPresenter(fetchBannerUseCase: useCase, router: ShoesListBannerRouter(performer: vc)) 22 | vc.presenter = presenter 23 | presenter.eventReceiver = vc 24 | return vc 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /MVP-Sample/DataLayer/Repositories/Remote/RemoteBannerRepository.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RemoteBannerRepository.swift 3 | // MVP-Sample 4 | // 5 | // Created by NixonShih on 2019/4/14. 6 | // Copyright © 2019 NixonShih. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct RemoteBannerRepository: BannerRepositorySpec where AnyNetworkFetchable: NetworkFetchable, AnyNetworkFetchable.DataModel == [RemoteBannerModel] { 12 | 13 | init(fetcher: AnyNetworkFetchable) { 14 | self.fetcher = fetcher 15 | } 16 | 17 | func fetchBanners(_ completionHandler: @escaping FetchBannerCompletionHandler) { 18 | fetcher.fire { (result) in 19 | switch result { 20 | case .success(let dataModel): 21 | completionHandler(Result.success(dataModel)) 22 | case .failure(let error): 23 | completionHandler(Result.failure(error)) 24 | } 25 | } 26 | } 27 | 28 | // MARK: private 29 | 30 | private let fetcher: AnyNetworkFetchable 31 | } 32 | -------------------------------------------------------------------------------- /MVP-SampleTests/MVP_SampleTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MVP_SampleTests.swift 3 | // MVP-SampleTests 4 | // 5 | // Created by NixonShih on 2018/11/11. 6 | // Copyright © 2018年 NixonShih. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import MVP_Sample 11 | 12 | class MVP_SampleTests: XCTestCase { 13 | 14 | override func setUp() { 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | } 21 | 22 | func testExample() { 23 | // This is an example of a functional test case. 24 | // Use XCTAssert and related functions to verify your tests produce the correct results. 25 | } 26 | 27 | func testPerformanceExample() { 28 | // This is an example of a performance test case. 29 | self.measure { 30 | // Put the code you want to measure the time of here. 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Nixon Shih 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.xcuserstate 3 | 4 | # Xcode 5 | # 6 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 7 | 8 | ## Build generated 9 | build/ 10 | DerivedData/ 11 | 12 | ## Various settings 13 | *.pbxuser 14 | !default.pbxuser 15 | *.mode1v3 16 | !default.mode1v3 17 | *.mode2v3 18 | !default.mode2v3 19 | *.perspectivev3 20 | !default.perspectivev3 21 | xcuserdata/ 22 | 23 | ## Other 24 | *.moved-aside 25 | *.xccheckout 26 | *.xcscmblueprint 27 | 28 | ## Obj-C/Swift specific 29 | *.hmap 30 | *.ipa 31 | *.dSYM.zip 32 | *.dSYM 33 | 34 | ## Playgrounds 35 | timeline.xctimeline 36 | playground.xcworkspace 37 | 38 | # Swift Package Manager 39 | # 40 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 41 | # Packages/ 42 | # Package.pins 43 | .build/ 44 | 45 | # CocoaPods 46 | # 47 | # We recommend against adding the Pods directory to your .gitignore. However 48 | # you should judge for yourself, the pros and cons are mentioned at: 49 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 50 | # 51 | Pods/ -------------------------------------------------------------------------------- /MVP-Sample/DataLayer/Repositories/Cache/CacheShoesRepository.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CacheShoesRepository.swift 3 | // MVP-Sample 4 | // 5 | // Created by Nixon.Shih on 2019/4/11. 6 | // Copyright © 2019 NixonShih. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct CacheShoesRepository: ShoesRepositorySpec { 12 | 13 | init(remoteRepository: ShoesRepositorySpec, localRepository: LocalShoesRepositorySpec) { 14 | self.remoteRepository = remoteRepository 15 | self.localRepository = localRepository 16 | } 17 | 18 | func fetchShoes(_ completionHandler: @escaping FetchShoesCompletionHandler) { 19 | remoteRepository.fetchShoes { (result) in 20 | switch result { 21 | case .success(let dataModel): 22 | self.localRepository.save(shoes: dataModel, completionHandler: nil) 23 | completionHandler(result) 24 | case .failure(_): 25 | self.localRepository.fetchShoes(completionHandler) 26 | } 27 | } 28 | } 29 | 30 | // MARK: private 31 | 32 | private var remoteRepository: ShoesRepositorySpec 33 | private var localRepository: LocalShoesRepositorySpec 34 | } 35 | -------------------------------------------------------------------------------- /MVP-Sample/ViewLayer/CustomView/Loading/LoadingImageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoadingImageView.swift 3 | // MVP-Sample 4 | // 5 | // Created by NixonShih on 2018/11/19. 6 | // Copyright © 2018 NixonShih. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class LoadingImageView: UIImageView { 12 | 13 | // MARK: Initialize 14 | 15 | override init(frame: CGRect) { 16 | super.init(frame: frame) 17 | customSetting() 18 | } 19 | 20 | required init?(coder aDecoder: NSCoder) { 21 | super.init(coder: aDecoder) 22 | customSetting() 23 | } 24 | 25 | private override init(image: UIImage?, highlightedImage: UIImage? = nil) { 26 | super.init(image: image, highlightedImage: highlightedImage) 27 | customSetting() 28 | } 29 | 30 | // MARK: Private methods 31 | 32 | private func customSetting() { 33 | 34 | var images = [UIImage]() 35 | 36 | for i in 0...29 { 37 | guard let image = UIImage(named: "frame-\(i)") else { continue } 38 | images.append(image) 39 | } 40 | 41 | contentMode = .center 42 | animationImages = images 43 | animationDuration = 0.8 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /MVP-Sample/ViewLayer/Scenes/ShoesList/ShoesListTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShoesListTableViewCell.swift 3 | // MVP-Sample 4 | // 5 | // Created by NixonShih on 2018/11/10. 6 | // Copyright © 2018 NixonShih. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct ShoesListCellModel { 12 | let title: String 13 | let price: String 14 | let date: String 15 | let imageName: String 16 | } 17 | 18 | class ShoesListTableViewCell: UITableViewCell, SetReuseViewModelable { 19 | 20 | @IBOutlet weak var shoesImageView: UIImageView! 21 | @IBOutlet weak var titleLabel: UILabel! 22 | @IBOutlet weak var dateLabel: UILabel! 23 | @IBOutlet weak var priceLabel: UILabel! 24 | 25 | func setReuseViewMode(_ viewModel: ShoesListCellModel) { 26 | shoesImageView.image = UIImage(named: viewModel.imageName) 27 | titleLabel.text = viewModel.title 28 | dateLabel.text = viewModel.date 29 | priceLabel.text = viewModel.price 30 | } 31 | 32 | required init?(coder aDecoder: NSCoder) { 33 | super.init(coder: aDecoder) 34 | setup() 35 | } 36 | 37 | // MARK: private 38 | 39 | private func setup() { 40 | selectionStyle = .none 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /MVP-Sample/ViewLayer/Scenes/ShoesDetail/ShoesDetailInfoTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShoesDetailInfoTableViewCell.swift 3 | // MVP-Sample 4 | // 5 | // Created by NixonShih on 2019/4/13. 6 | // Copyright © 2019 NixonShih. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct ShoesDetailInfoCellModel { 12 | let title: String 13 | let price: String 14 | let date: String 15 | let nickName: String 16 | } 17 | 18 | class ShoesDetailInfoTableViewCell: UITableViewCell, SetReuseViewModelable { 19 | 20 | @IBOutlet weak var nickNameLabel: UILabel! 21 | @IBOutlet weak var dateLabel: UILabel! 22 | @IBOutlet weak var titleLabel: UILabel! 23 | @IBOutlet weak var priceLabel: UILabel! 24 | 25 | func setReuseViewMode(_ viewModel: ShoesDetailInfoCellModel) { 26 | nickNameLabel.text = viewModel.nickName 27 | titleLabel.text = viewModel.title 28 | dateLabel.text = viewModel.date 29 | priceLabel.text = viewModel.price 30 | } 31 | 32 | required init?(coder aDecoder: NSCoder) { 33 | super.init(coder: aDecoder) 34 | setup() 35 | } 36 | 37 | // MARK: private 38 | 39 | private func setup() { 40 | selectionStyle = .none 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /MVP-Sample/ViewLayer/CustomView/FailHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FailHeaderView.swift 3 | // MVP-Sample 4 | // 5 | // Created by NixonShih on 2018/11/21. 6 | // Copyright © 2018 NixonShih. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class FailHeaderView: UIView { 12 | 13 | // MARK: Initialize 14 | 15 | convenience init() { 16 | self.init(frame: .zero) 17 | } 18 | 19 | override init(frame: CGRect) { 20 | super.init(frame: frame) 21 | customSetting() 22 | } 23 | 24 | required init?(coder aDecoder: NSCoder) { 25 | super.init(coder: aDecoder) 26 | customSetting() 27 | } 28 | 29 | // MARK: Private methods 30 | 31 | private func customSetting() { 32 | 33 | backgroundColor = ThemeColor.darkRed.color 34 | 35 | let label = UILabel() 36 | label.text = "Failure: please try again later" 37 | label.textColor = ThemeColor.pureWhite.color 38 | addSubview(label) 39 | label.translatesAutoresizingMaskIntoConstraints = false 40 | label.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true 41 | label.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /MVP-Sample/Other/Util/NotificationUtility.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotificationUtility.swift 3 | // MVP-Sample 4 | // 5 | // Created by Nixon.Shih on 2019/4/10. 6 | // Copyright © 2019 NixonShih. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol Notifiable { 12 | 13 | var name: Notification.Name { get } 14 | 15 | func observed(by observer: T, withSelector selector: Selector, object: Any?) 16 | func post(object: Any?, userInfo: [AnyHashable: Any]?) 17 | func remove(observer: T) 18 | } 19 | 20 | extension Notifiable { 21 | 22 | func observed(by observer: T, withSelector selector: Selector, object: Any? = nil) { 23 | NotificationCenter.default.addObserver(observer, selector: selector, name: name, object: object) 24 | } 25 | 26 | func post(object: Any? = nil, userInfo: [AnyHashable: Any]? = nil) { 27 | NotificationCenter.default.post(name: name, object: object, userInfo: userInfo) 28 | } 29 | 30 | func remove(observer: T) { 31 | NotificationCenter.default.removeObserver(observer, name: name, object: nil) 32 | } 33 | 34 | var name: Notification.Name { 35 | let name = "kNotificationKeyOf\(String(describing: Self.self))_\(String(describing: self))" 36 | return Notification.Name(name) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /MVP-Sample/DataLayer/Repositories/Remote/RemoteShoesRepository.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RemoteShoesRepository.swift 3 | // MVP-Sample 4 | // 5 | // Created by Nixon.Shih on 2019/4/11. 6 | // Copyright © 2019 NixonShih. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct RemoteShoesRepository: ShoesRepositorySpec where AnyNetworkFetchable: NetworkFetchable, AnyNetworkFetchable.DataModel == [ShoesModel] { 12 | 13 | init(fetcher: AnyNetworkFetchable) { 14 | self.fetcher = fetcher 15 | } 16 | 17 | func fetchShoes(_ completionHandler: @escaping FetchShoesCompletionHandler) { 18 | fetcher.fire { (result) in 19 | switch result { 20 | case .success(let dataModel): 21 | completionHandler(Result.success(dataModel.sorted(by: self.sortedShose))) 22 | case .failure(let error): 23 | completionHandler(Result.failure(error)) 24 | } 25 | } 26 | } 27 | 28 | // MARK: private 29 | 30 | private let fetcher: AnyNetworkFetchable 31 | 32 | private let dateFormatter: DateFormatter = { 33 | let formatter = DateFormatter() 34 | formatter.dateFormat = "yyyy-MM-dd" 35 | return formatter 36 | }() 37 | 38 | private func sortedShose(shoes1: ShoesModel, shoes2: ShoesModel) -> Bool { 39 | return dateFormatter.date(from: shoes1.date)! > dateFormatter.date(from: shoes2.date)! 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /MVP-Sample/ViewLayer/Scenes/ShoesList/ShoesListViewBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShoesListViewBuilder.swift 3 | // MVP-Sample 4 | // 5 | // Created by Nixon.Shih on 2019/4/12. 6 | // Copyright © 2019 NixonShih. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// The builder of ShoesListViewController 12 | struct ShoesListViewBuilder: ViewBuilderSpec { 13 | 14 | /// builds ViewController and injects dependency of components. 15 | func build() -> ShoesListViewController { 16 | 17 | let remoteRepository = RemoteShoesRepository(fetcher: ShoesListFetcher()) 18 | let cacheRepository = CacheShoesRepository(remoteRepository: remoteRepository, localRepository: LocalShoesRepository()) 19 | let fetchLocalShoesUseCaes = FetchShoesUseCase(repository: LocalShoesRepository()) 20 | let fetchShoesUseCaes = FetchShoesUseCase(repository: cacheRepository) 21 | let vc = UIStoryboard.main.instantiateViewController(withClass: ShoesListViewController.self) 22 | vc.bannerViewController = ShoesListBannerBuilder().build() 23 | vc.presenter = ShoesListPresenter( 24 | fetchShoesUseCase: fetchShoesUseCaes, 25 | fetchLocalShoesUseCaseSpec: fetchLocalShoesUseCaes, 26 | router: ShoesListRouter(performer: vc), 27 | bannerPresenter: vc.bannerViewController.presenter, 28 | nameProvider: ProjectNameHelper() 29 | ) 30 | vc.presenter.eventReceiver = vc 31 | return vc 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /MVP-Sample/DataLayer/Repositories/Remote/BannerFetcher.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BannerFetcher.swift 3 | // MVP-Sample 4 | // 5 | // Created by NixonShih on 2019/4/14. 6 | // Copyright © 2019 NixonShih. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct BannerFetcher: NetworkFetchable { 12 | 13 | typealias DataModel = [RemoteBannerModel] 14 | 15 | func fire(_ completionHandler: @escaping (Result<[RemoteBannerModel], NetworkError>) -> ()) { 16 | 17 | DispatchQueue.main.asyncAfter(deadline: .now() + 2) { 18 | let data = self.getDummyData() 19 | do { 20 | let response = try JSONDecoder().decode(RemoteBaseModel.self, from: data) 21 | guard response.code == 0 else { 22 | completionHandler(Result.failure(NetworkError.serviceError)) 23 | return 24 | } 25 | completionHandler(Result.success(response.body)) 26 | } catch { 27 | completionHandler(Result.failure(NetworkError.decode)) 28 | } 29 | } 30 | } 31 | 32 | // MARK: private 33 | 34 | private func getDummyData() -> Data { 35 | 36 | guard 37 | let url = Bundle.main.url(forResource: "banner_remote_source", withExtension: "json"), 38 | let data = try? Data(contentsOf: url) else { 39 | fatalError("Couldn't get dummy data.") 40 | } 41 | return data 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /MVP-Sample/DataLayer/Repositories/Remote/ShoesListFetcher.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShoesListFetcher.swift 3 | // MVP-Sample 4 | // 5 | // Created by NixonShih on 2018/11/19. 6 | // Copyright © 2018 NixonShih. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct ShoesListFetcher: NetworkFetchable { 12 | 13 | typealias DataModel = [ShoesModel] 14 | 15 | func fire(_ completionHandler: @escaping (Result<[ShoesModel], NetworkError>) -> ()) { 16 | 17 | DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) { 18 | let data = self.getDummyData() 19 | do { 20 | let response = try JSONDecoder().decode(RemoteBaseModel.self, from: data) 21 | guard response.code == 0 else { 22 | completionHandler(Result.failure(NetworkError.serviceError)) 23 | return 24 | } 25 | completionHandler(Result.success(response.body)) 26 | } catch { 27 | completionHandler(Result.failure(NetworkError.decode)) 28 | } 29 | } 30 | } 31 | 32 | // MARK: private 33 | 34 | private func getDummyData() -> Data { 35 | 36 | guard 37 | let url = Bundle.main.url(forResource: "shoes_list_remote_source", withExtension: "json"), 38 | let data = try? Data(contentsOf: url) else { 39 | fatalError("Couldn't get dummy data.") 40 | } 41 | return data 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /MVP-Sample/DomainLayer/UseCases/FetchBannerUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FetchBannerUseCase.swift 3 | // MVP-Sample 4 | // 5 | // Created by NixonShih on 2019/4/14. 6 | // Copyright © 2019 NixonShih. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct FetchBannerUseCase: FetchDataUseCaseSpec { 12 | 13 | typealias DataModel = [BannerModel] 14 | 15 | init(repository: BannerRepositorySpec) { 16 | self.repository = repository 17 | } 18 | 19 | func fetchDataModel(_ completionHandler: @escaping FetchDataModelUseCaseCompletionHandler) { 20 | DispatchQueue.global().async { 21 | self.repository.fetchBanners { (result) in 22 | switch result { 23 | case .success(let dataModel): 24 | DispatchQueue.main.async(execute: { 25 | completionHandler(Result.success(dataModel.map(self.mapBanner).shuffled())) 26 | }) 27 | case .failure(let error): 28 | DispatchQueue.main.async { 29 | completionHandler(Result.failure(error)) 30 | } 31 | } 32 | } 33 | } 34 | } 35 | 36 | // MARK: private 37 | 38 | private let repository: BannerRepositorySpec 39 | 40 | private func mapBanner(_ remoteBanner: RemoteBannerModel) -> BannerModel { 41 | return BannerModel(imageName: remoteBanner.imageName, url: URL(string: remoteBanner.url)) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /MVP-Sample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UIStatusBarStyle 30 | UIStatusBarStyleLightContent 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /MVP-Sample/ViewLayer/CustomView/Loading/LoadingViewManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoadingViewManager.swift 3 | // MVP-Sample 4 | // 5 | // Created by NixonShih on 2018/11/19. 6 | // Copyright © 2018 NixonShih. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// Make a loading indicator 12 | class LoadingViewManager { 13 | 14 | /// Show loading indicator for R88 15 | class func show(autoClose time: TimeInterval = 0) { 16 | 17 | guard shared.window == nil else { return } 18 | shared.makeWindow() 19 | } 20 | 21 | /// Dismiss loading indicator for R88 22 | class func dismiss() { 23 | 24 | guard shared.window != nil else { return } 25 | shared.window = nil 26 | } 27 | 28 | // MARK: - Private 29 | 30 | private static let shared: LoadingViewManager = LoadingViewManager() 31 | private var window: UIWindow? 32 | 33 | private init() { } 34 | 35 | private func makeWindow() { 36 | 37 | let loadingView = LoadingImageView(frame: CGRect.zero) 38 | loadingView.startAnimating() 39 | 40 | let vc = BaseViewController() 41 | vc.view.backgroundColor = UIColor(white: 0, alpha: 0.8) 42 | vc.view.addSubview(loadingView) 43 | 44 | loadingView.translatesAutoresizingMaskIntoConstraints = false 45 | loadingView.centerXAnchor.constraint(equalTo: vc.view.centerXAnchor).isActive = true 46 | loadingView.centerYAnchor.constraint(equalTo: vc.view.centerYAnchor).isActive = true 47 | 48 | window = UIWindow(frame: UIScreen.main.bounds) 49 | window?.rootViewController = vc 50 | window?.windowLevel = UIWindow.Level(1300) 51 | window?.makeKeyAndVisible() 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![swift](https://img.shields.io/badge/language-swift-red.svg)](https://developer.apple.com/swift/) 2 | 3 | # Swift-MVP-Sample 4 | 5 | It's an iOS simple project that how I implement `MVP` (Model-View-Presenter) and `Clean Architecture` in Swift. 6 | 7 | ## Requirements 8 | 9 | - Xcode 10.2+ 10 | - Swift 5.0+ 11 | 12 | ## Architecture 13 | 14 | ![](architecture.jpg) 15 | 16 | ### View Layer (MVP) 17 | - `View` - Displays information from the `Presenter` and sends user interactions back to the `Presenter`. 18 | - `Presenter` - Contains the presentation logic and tells the `View` what to present 19 | - `ViewBuilder` - The `Builder’s` responsibility is to instantiate a specific `View` and injects the dependency for all components. 20 | - `Router` - Handles navigation logic for which screen should appear and when. 21 | 22 | ### Domain Layer 23 | - `UseCase` - Contains the business logic for a specific use case in the project. They are view agnostic and can be consumed by one or many `Presenters`. 24 | - `Model` - Simple data model objects. 25 | 26 | ### Data Layer 27 | - `Repository` - Query objects from different data sources (Core Data, Realm, web server, etc.) with a only single-entry point. 28 | 29 | ## References 30 | 31 | - [iOS: MVP clean architecture in Tiendeo app](https://medium.com/tiendeo-tech/ios-mvp-clean-architecture-in-tiendeo-app-a8a597c49bb9) 32 | - [Library - iOS - MVP + Clean Architecture Demo](https://github.com/FortechRomania/ios-mvp-clean-architecture/) 33 | - [The Clean Architecture, by Uncle Bob](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) 34 | - [Test Double](https://www.martinfowler.com/bliki/TestDouble.html) 35 | 36 | ## Author 37 | 38 | Nixon Shih, powerwolf543@gmail.com 39 | 40 | ## License 41 | 42 | Swift-MVP-Sample is available under the MIT license. See the LICENSE file for more info. 43 | -------------------------------------------------------------------------------- /MVP-SampleTests/DomainLayer/FetchShoesUseCaseTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FetchShoesUseCaseTests.swift 3 | // MVP-SampleTests 4 | // 5 | // Created by NixonShih on 2019/4/16. 6 | // Copyright © 2019 NixonShih. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import MVP_Sample 11 | 12 | class FetchShoesUseCaseTests: XCTestCase { 13 | 14 | var repository = MockFetchShoesRepository() 15 | lazy var fetchShoesUseCase = FetchShoesUseCase(repository: repository) 16 | 17 | func testSuccessCallBack() { 18 | 19 | let shoes = [ShoesModel.newShoes] 20 | let expectedResult: Result<[ShoesModel], Error> = .success(shoes) 21 | repository.expectedResult = expectedResult 22 | let completionHandlerExpectation = expectation(description: "Fetch shoes expectation") 23 | 24 | fetchShoesUseCase.fetchDataModel { (result) in 25 | switch result { 26 | case .success(let model): 27 | XCTAssertEqual(model, shoes) 28 | completionHandlerExpectation.fulfill() 29 | case .failure: break 30 | } 31 | } 32 | waitForExpectations(timeout: 1, handler: nil) 33 | } 34 | 35 | 36 | func testFailCallBack() { 37 | 38 | let theError = MockFetchShoesRepositoryError.fail 39 | let expectedResult: Result<[ShoesModel], Error> = .failure(theError) 40 | repository.expectedResult = expectedResult 41 | let completionHandlerExpectation = expectation(description: "Fetch shoes expectation") 42 | 43 | fetchShoesUseCase.fetchDataModel { (result) in 44 | switch result { 45 | case .success: break 46 | case .failure(let error): 47 | switch error { 48 | case MockFetchShoesRepositoryError.fail: 49 | completionHandlerExpectation.fulfill() 50 | default: break 51 | } 52 | } 53 | } 54 | waitForExpectations(timeout: 1, handler: nil) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /MVP-Sample/ViewLayer/Scenes/ShoesDetail/ShoesDetailPresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShoesDetailPresenter.swift 3 | // MVP-Sample 4 | // 5 | // Created by NixonShih on 2019/4/13. 6 | // Copyright © 2019 NixonShih. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol ShoesDetailViewEventReceiverable: class { 12 | func receivedEventOfSetupViews(with setupModel: ShoesDetailViewSetupModel) 13 | } 14 | 15 | protocol ShoesDetailPresenterSpec { 16 | 17 | var eventReceiver: ShoesDetailViewEventReceiverable? { get set } 18 | var rows: Int { get } 19 | 20 | func setup() 21 | func getRowType(by row: Int) -> ShoesDetailRow 22 | func getInfoCellModel() -> ShoesDetailInfoCellModel 23 | func getDescriptionCellModel() -> ShoesDetailDescriptionCellModel 24 | } 25 | 26 | struct ShoesDetailViewSetupModel { 27 | let title: String 28 | let productImageName: String 29 | } 30 | 31 | class ShoesDetailPresenter: ShoesDetailPresenterSpec { 32 | 33 | var rows: Int { return 2 } 34 | weak var eventReceiver: ShoesDetailViewEventReceiverable? 35 | 36 | init(shoes: ShoesModel) { 37 | self.shoes = shoes 38 | } 39 | 40 | func setup() { 41 | let setupModel = ShoesDetailViewSetupModel(title: "Detail", productImageName: shoes.imageName) 42 | eventReceiver?.receivedEventOfSetupViews(with: setupModel) 43 | } 44 | 45 | func getRowType(by row: Int) -> ShoesDetailRow { 46 | guard let type = ShoesDetailRow(rawValue: row) 47 | else { fatalError("Unknown row.") } 48 | return type 49 | } 50 | 51 | func getInfoCellModel() -> ShoesDetailInfoCellModel { 52 | return ShoesDetailInfoCellModel( 53 | title: shoes.title, price: "$" + String(shoes.price), date: shoes.date, nickName: shoes.nickname ?? "" 54 | ) 55 | } 56 | 57 | func getDescriptionCellModel() -> ShoesDetailDescriptionCellModel { 58 | return ShoesDetailDescriptionCellModel(description: shoes.description ?? "") 59 | } 60 | 61 | // MARK: Private 62 | 63 | private let shoes: ShoesModel 64 | } 65 | -------------------------------------------------------------------------------- /MVP-Sample/DataLayer/Repositories/Local/LocalShoesRepository.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LocalShoesRepository.swift 3 | // MVP-Sample 4 | // 5 | // Created by Nixon.Shih on 2019/4/11. 6 | // Copyright © 2019 NixonShih. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol LocalShoesRepositorySpec: ShoesRepositorySpec { 12 | 13 | typealias SaveShoesCompletionHandler = (Result) -> () 14 | func save(shoes: [ShoesModel], completionHandler: SaveShoesCompletionHandler?) 15 | } 16 | 17 | enum LocalShoesRepositoryError: Error { 18 | case noLocalCache 19 | } 20 | 21 | /// Cache dataModel to userDefaults 22 | struct LocalShoesRepository: LocalShoesRepositorySpec { 23 | 24 | func save(shoes: [ShoesModel], completionHandler: SaveShoesCompletionHandler?) { 25 | do { 26 | let data = try JSONEncoder().encode(shoes) 27 | UserDefaults.standard.set(data, forKey: LocalShoesRepository.localShoesListPersistKey) 28 | completionHandler?(Result.success(())) 29 | } catch { 30 | completionHandler?(Result.failure(error)) 31 | } 32 | } 33 | 34 | func fetchShoes(_ completionHandler: @escaping FetchShoesCompletionHandler) { 35 | 36 | guard let data = UserDefaults.standard.data(forKey: LocalShoesRepository.localShoesListPersistKey) else { 37 | completionHandler(Result.failure(LocalShoesRepositoryError.noLocalCache)) 38 | return 39 | } 40 | 41 | do { 42 | let dataModel = try JSONDecoder().decode([ShoesModel].self, from: data) 43 | completionHandler(Result.success(dataModel)) 44 | } catch { 45 | completionHandler(Result.failure(error)) 46 | } 47 | } 48 | 49 | static func removeAll() { 50 | UserDefaults.standard.removeObject(forKey: localShoesListPersistKey) 51 | } 52 | 53 | // MARK: private 54 | 55 | private static let localShoesListPersistKey = "kLocalShoesListPersistKey" 56 | 57 | private func getDummyData() -> Data { 58 | 59 | guard 60 | let url = Bundle.main.url(forResource: "shoes_list_firt_time", withExtension: "json"), 61 | let data = try? Data(contentsOf: url) else { 62 | fatalError("Couldn't get dummy data.") 63 | } 64 | return data 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /MVP-Sample/Other/Extension/UICollectionView+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionView+Extensions.swift 3 | // MVP-Sample 4 | // 5 | // Created by NixonShih on 2018/11/8. 6 | // Copyright © 2018 NixonShih. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UICollectionView { 12 | 13 | func dequeueReusableCell(withClass name: T.Type, for indexPath: IndexPath) -> T { 14 | guard let cell = dequeueReusableCell(withReuseIdentifier: String(describing: name), for: indexPath) as? T else { 15 | fatalError("Dequeue error: Couldn't find UICollectionViewCell for \(String(describing: name))") 16 | } 17 | return cell 18 | } 19 | 20 | func dequeueReusableSupplementaryView(ofKind kind: String, withClass name: T.Type, for indexPath: IndexPath) -> T { 21 | guard let cell = dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: String(describing: name), for: indexPath) as? T else { 22 | fatalError("Dequeue error: Couldn't find UICollectionReusableView for \(String(describing: name))") 23 | } 24 | return cell 25 | } 26 | 27 | func register(supplementaryViewOfKind kind: String, withClass name: T.Type) { 28 | register(T.self, forSupplementaryViewOfKind: kind, withReuseIdentifier: String(describing: name)) 29 | } 30 | 31 | func register(nib: UINib?, forCellWithClass name: T.Type) { 32 | register(nib, forCellWithReuseIdentifier: String(describing: name)) 33 | } 34 | 35 | func register(cellWithClass name: T.Type) { 36 | register(T.self, forCellWithReuseIdentifier: String(describing: name)) 37 | } 38 | 39 | func register(nib: UINib?, forSupplementaryViewOfKind kind: String, withClass name: T.Type) { 40 | register(nib, forSupplementaryViewOfKind: kind, withReuseIdentifier: String(describing: name)) 41 | } 42 | 43 | func register(nibWithCellClass name: T.Type, at bundleClass: AnyClass? = nil) { 44 | 45 | let identifier = String(describing: name) 46 | var bundle: Bundle? 47 | 48 | if let bundleName = bundleClass { 49 | bundle = Bundle(for: bundleName) 50 | } 51 | register(UINib(nibName: identifier, bundle: bundle), forCellWithReuseIdentifier: identifier) 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /MVP-SampleTests/DomainLayer/FetchBannerUseCaseTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FetchBannerUseCaseTests.swift 3 | // MVP-SampleTests 4 | // 5 | // Created by NixonShih on 2019/4/16. 6 | // Copyright © 2019 NixonShih. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import MVP_Sample 11 | 12 | class FetchBannerUseCaseTests: XCTestCase { 13 | 14 | var repository = MockRemoteBannerRepository() 15 | lazy var fetchBannersUseCase = FetchBannerUseCase(repository: repository) 16 | 17 | func testSuccessCallBack() { 18 | 19 | let banners = [RemoteBannerModel(imageName: "123", url: ""), 20 | RemoteBannerModel(imageName: "456", url: "789"), 21 | RemoteBannerModel(imageName: "098", url: "http://www.a.com")] 22 | let expectedResult: Result<[RemoteBannerModel], Error> = .success(banners) 23 | repository.expectedResult = expectedResult 24 | let expectedBanners = [BannerModel(imageName: "123", url: nil), 25 | BannerModel(imageName: "456", url: URL(string: "789")), 26 | BannerModel(imageName: "098", url: URL(string: "http://www.a.com"))] 27 | let completionHandlerExpectation = expectation(description: "Fetch shoes expectation") 28 | 29 | fetchBannersUseCase.fetchDataModel { (result) in 30 | switch result { 31 | case .success(let model): 32 | XCTAssertEqual(model, expectedBanners) 33 | completionHandlerExpectation.fulfill() 34 | case .failure: break 35 | } 36 | } 37 | waitForExpectations(timeout: 1, handler: nil) 38 | } 39 | 40 | 41 | func testFailCallBack() { 42 | 43 | let theError = MockRemoteBannerRepositoryError.fail 44 | let expectedResult: Result<[RemoteBannerModel], Error> = .failure(theError) 45 | repository.expectedResult = expectedResult 46 | let completionHandlerExpectation = expectation(description: "Fetch shoes expectation") 47 | 48 | fetchBannersUseCase.fetchDataModel { (result) in 49 | switch result { 50 | case .success: break 51 | case .failure(let error): 52 | switch error { 53 | case MockRemoteBannerRepositoryError.fail: 54 | completionHandlerExpectation.fulfill() 55 | default: break 56 | } 57 | } 58 | } 59 | waitForExpectations(timeout: 1, handler: nil) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /MVP-Sample/Other/Extension/UITableView+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITableView+Extensions.swift 3 | // MVP-Sample 4 | // 5 | // Created by NixonShih on 2018/11/8. 6 | // Copyright © 2018 NixonShih. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UITableView { 12 | 13 | func dequeueReusableCell(withClass name: T.Type) -> T { 14 | guard let cell = dequeueReusableCell(withIdentifier: String(describing: name)) as? T else { 15 | fatalError("Dequeue error: Couldn't find UITableViewCell for \(String(describing: name))") 16 | } 17 | return cell 18 | } 19 | 20 | func dequeueReusableCell(withClass name: T.Type, for indexPath: IndexPath) -> T { 21 | guard let cell = dequeueReusableCell(withIdentifier: String(describing: name), for: indexPath) as? T else { 22 | fatalError("Dequeue error: Couldn't find UITableViewCell for \(String(describing: name))") 23 | } 24 | return cell 25 | } 26 | 27 | func dequeueReusableHeaderFooterView(withClass name: T.Type) -> T { 28 | guard let headerFooterView = dequeueReusableHeaderFooterView(withIdentifier: String(describing: name)) as? T else { 29 | fatalError("Dequeue error: Couldn't find UITableViewHeaderFooterView for \(String(describing: name))") 30 | } 31 | return headerFooterView 32 | } 33 | 34 | func register(nib: UINib?, withHeaderFooterViewClass name: T.Type) { 35 | register(nib, forHeaderFooterViewReuseIdentifier: String(describing: name)) 36 | } 37 | 38 | func register(headerFooterViewClassWith name: T.Type) { 39 | register(T.self, forHeaderFooterViewReuseIdentifier: String(describing: name)) 40 | } 41 | 42 | func register(cellWithClass name: T.Type) { 43 | register(T.self, forCellReuseIdentifier: String(describing: name)) 44 | } 45 | 46 | func register(nib: UINib?, withCellClass name: T.Type) { 47 | register(nib, forCellReuseIdentifier: String(describing: name)) 48 | } 49 | 50 | func register(nibWithCellClass name: T.Type, at bundleClass: AnyClass? = nil) { 51 | 52 | let identifier = String(describing: name) 53 | var bundle: Bundle? 54 | 55 | if let bundleName = bundleClass { 56 | bundle = Bundle(for: bundleName) 57 | } 58 | register(UINib(nibName: identifier, bundle: bundle), forCellReuseIdentifier: identifier) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /MVP-Sample/ViewLayer/Scenes/ShoesList/Banner/ShoesListBannerPresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShoesListBannerPresenter.swift 3 | // MVP-Sample 4 | // 5 | // Created by NixonShih on 2019/4/14. 6 | // Copyright © 2019 NixonShih. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol ShoesListBannerViewEventReceiverable: class { 12 | func receivedEventOfStartLoading() 13 | func receivedEventOfReload() 14 | func receivedEventOfLoadFail() 15 | } 16 | 17 | protocol ShoesListBannerPresenterSpec { 18 | 19 | var eventReceiver: ShoesListBannerViewEventReceiverable? { get set } 20 | var countOfItem: Int { get } 21 | 22 | func setup() 23 | func getImageName(by index: Int) -> String 24 | func didSelect(at index: Int) 25 | } 26 | 27 | class ShoesListBannerPresenter: ShoesListBannerPresenterSpec where AnyFetchBannerUseCase: FetchDataUseCaseSpec, AnyFetchBannerUseCase.DataModel == [BannerModel] { 28 | 29 | init(fetchBannerUseCase: AnyFetchBannerUseCase, router: ShoesListBannerRouterSpec) { 30 | self.fetchBannerUseCase = fetchBannerUseCase 31 | self.router = router 32 | } 33 | 34 | func setup() { 35 | eventReceiver?.receivedEventOfStartLoading() 36 | } 37 | 38 | weak var eventReceiver: ShoesListBannerViewEventReceiverable? 39 | var countOfItem: Int { return banners.count } 40 | 41 | func getImageName(by index: Int) -> String { 42 | return banners[index].imageName 43 | } 44 | 45 | func didSelect(at index: Int) { 46 | guard let url = banners[index].url else { return } 47 | router.pushWebView(with: url) 48 | } 49 | 50 | // MARK: private 51 | 52 | private var banners: [BannerModel] = [] 53 | private let fetchBannerUseCase: AnyFetchBannerUseCase 54 | private let router: ShoesListBannerRouterSpec 55 | 56 | private func fetchBanners() { 57 | if banners.isEmpty { self.eventReceiver?.receivedEventOfStartLoading() } 58 | 59 | fetchBannerUseCase.fetchDataModel { [weak self] (result) in 60 | guard let self = self else { return } 61 | switch result { 62 | case .success(let dataModel): 63 | self.banners = dataModel 64 | self.eventReceiver?.receivedEventOfReload() 65 | case .failure: 66 | self.eventReceiver?.receivedEventOfLoadFail() 67 | } 68 | } 69 | } 70 | } 71 | 72 | extension ShoesListBannerPresenter: ShoesListSubpresenterOfBannerSpec { 73 | 74 | func receivedEventOfReloadFromSuperPresenter() { 75 | fetchBanners() 76 | } 77 | } 78 | --------------------------------------------------------------------------------