diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..97db408 --- /dev/null +++ b/.gitignore @@ -0,0 +1,68 @@ +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## Build generated +build/ +DerivedData/ + +## Various settings +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata/ + +## Other +*.moved-aside +*.xccheckout +*.xcscmblueprint +*.DS_Store + +## Obj-C/Swift specific +*.hmap +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +.build/ + +# CocoaPods +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# +Pods/ + +# Carthage +# +# Add this line if you want to avoid checking in source code from Carthage dependencies. + +Carthage/Checkouts +Carthage/Build + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output \ No newline at end of file diff --git a/BaseMVC.xcodeproj/project.pbxproj b/BaseMVC.xcodeproj/project.pbxproj new file mode 100644 index 0000000..b4a932e --- /dev/null +++ b/BaseMVC.xcodeproj/project.pbxproj @@ -0,0 +1,578 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 6C7798886244F4E294474CFD /* Pods_BaseMVC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 581DCC4B99E729E5223C5E6F /* Pods_BaseMVC.framework */; }; + 8234BE651EF9887D009A5DE5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8234BE231EF9887D009A5DE5 /* AppDelegate.swift */; }; + 8234BE661EF9887D009A5DE5 /* BaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8234BE251EF9887D009A5DE5 /* BaseViewController.swift */; }; + 8234BE6B1EF9887D009A5DE5 /* StartViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8234BE2A1EF9887D009A5DE5 /* StartViewController.swift */; }; + 8234BE6D1EF9887D009A5DE5 /* Color+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8234BE2D1EF9887D009A5DE5 /* Color+Ext.swift */; }; + 8234BE6E1EF9887D009A5DE5 /* Date+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8234BE2E1EF9887D009A5DE5 /* Date+Ext.swift */; }; + 8234BE6F1EF9887D009A5DE5 /* String+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8234BE2F1EF9887D009A5DE5 /* String+Ext.swift */; }; + 8234BE701EF9887D009A5DE5 /* UITableView+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8234BE301EF9887D009A5DE5 /* UITableView+Ext.swift */; }; + 8234BE711EF9887D009A5DE5 /* UIView+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8234BE311EF9887D009A5DE5 /* UIView+Ext.swift */; }; + 8234BE721EF9887D009A5DE5 /* UIViewController+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8234BE321EF9887D009A5DE5 /* UIViewController+Ext.swift */; }; + 8234BE731EF9887D009A5DE5 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8234BE341EF9887D009A5DE5 /* Config.swift */; }; + 8234BE741EF9887D009A5DE5 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8234BE351EF9887D009A5DE5 /* Constants.swift */; }; + 8234BE751EF9887D009A5DE5 /* EnumDefine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8234BE361EF9887D009A5DE5 /* EnumDefine.swift */; }; + 8234BE761EF9887D009A5DE5 /* Func.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8234BE371EF9887D009A5DE5 /* Func.swift */; }; + 8234BE771EF9887D009A5DE5 /* Keys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8234BE381EF9887D009A5DE5 /* Keys.swift */; }; + 8234BE781EF9887D009A5DE5 /* LocalizableString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8234BE391EF9887D009A5DE5 /* LocalizableString.swift */; }; + 8234BE791EF9887D009A5DE5 /* StructDefine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8234BE3A1EF9887D009A5DE5 /* StructDefine.swift */; }; + 8234BE7A1EF9887D009A5DE5 /* ViewDefine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8234BE3B1EF9887D009A5DE5 /* ViewDefine.swift */; }; + 8234BE7B1EF9887D009A5DE5 /* Animator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8234BE3E1EF9887D009A5DE5 /* Animator.swift */; }; + 8234BE7C1EF9887D009A5DE5 /* RealmManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8234BE3F1EF9887D009A5DE5 /* RealmManager.swift */; }; + 8234BE7E1EF9887D009A5DE5 /* RStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8234BE421EF9887D009A5DE5 /* RStore.swift */; }; + 8234BE7F1EF9887D009A5DE5 /* RUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8234BE431EF9887D009A5DE5 /* RUser.swift */; }; + 8234BE801EF9887D009A5DE5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8234BE451EF9887D009A5DE5 /* Assets.xcassets */; }; + 8234BE811EF9887D009A5DE5 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8234BE461EF9887D009A5DE5 /* Localizable.strings */; }; + 8234BE821EF9887D009A5DE5 /* ApiClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8234BE4A1EF9887D009A5DE5 /* ApiClient.swift */; }; + 8234BE831EF9887D009A5DE5 /* AppRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8234BE4B1EF9887D009A5DE5 /* AppRouter.swift */; }; + 8234BE841EF9887D009A5DE5 /* OAuthHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8234BE4C1EF9887D009A5DE5 /* OAuthHandler.swift */; }; + 8234BE861EF9887D009A5DE5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8234BE501EF9887D009A5DE5 /* LaunchScreen.storyboard */; }; + 8234BE881EF9887D009A5DE5 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8234BE541EF9887D009A5DE5 /* Main.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 21D61AFBDF31D90808DB8DB3 /* Pods-BaseMVC.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BaseMVC.release.xcconfig"; path = "Pods/Target Support Files/Pods-BaseMVC/Pods-BaseMVC.release.xcconfig"; sourceTree = ""; }; + 581DCC4B99E729E5223C5E6F /* Pods_BaseMVC.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_BaseMVC.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 6E818EC5E2DBA2392086830C /* Pods-BaseMVC.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BaseMVC.debug.xcconfig"; path = "Pods/Target Support Files/Pods-BaseMVC/Pods-BaseMVC.debug.xcconfig"; sourceTree = ""; }; + 8234BE0D1EF987C4009A5DE5 /* BaseMVC.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BaseMVC.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 8234BE1C1EF987C4009A5DE5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 8234BE231EF9887D009A5DE5 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 8234BE251EF9887D009A5DE5 /* BaseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseViewController.swift; sourceTree = ""; }; + 8234BE2A1EF9887D009A5DE5 /* StartViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StartViewController.swift; sourceTree = ""; }; + 8234BE2D1EF9887D009A5DE5 /* Color+Ext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Color+Ext.swift"; sourceTree = ""; }; + 8234BE2E1EF9887D009A5DE5 /* Date+Ext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+Ext.swift"; sourceTree = ""; }; + 8234BE2F1EF9887D009A5DE5 /* String+Ext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Ext.swift"; sourceTree = ""; }; + 8234BE301EF9887D009A5DE5 /* UITableView+Ext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITableView+Ext.swift"; sourceTree = ""; }; + 8234BE311EF9887D009A5DE5 /* UIView+Ext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Ext.swift"; sourceTree = ""; }; + 8234BE321EF9887D009A5DE5 /* UIViewController+Ext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIViewController+Ext.swift"; sourceTree = ""; }; + 8234BE341EF9887D009A5DE5 /* Config.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; }; + 8234BE351EF9887D009A5DE5 /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; + 8234BE361EF9887D009A5DE5 /* EnumDefine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnumDefine.swift; sourceTree = ""; }; + 8234BE371EF9887D009A5DE5 /* Func.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Func.swift; sourceTree = ""; }; + 8234BE381EF9887D009A5DE5 /* Keys.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Keys.swift; sourceTree = ""; }; + 8234BE391EF9887D009A5DE5 /* LocalizableString.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalizableString.swift; sourceTree = ""; }; + 8234BE3A1EF9887D009A5DE5 /* StructDefine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StructDefine.swift; sourceTree = ""; }; + 8234BE3B1EF9887D009A5DE5 /* ViewDefine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewDefine.swift; sourceTree = ""; }; + 8234BE3E1EF9887D009A5DE5 /* Animator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Animator.swift; sourceTree = ""; }; + 8234BE3F1EF9887D009A5DE5 /* RealmManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealmManager.swift; sourceTree = ""; }; + 8234BE421EF9887D009A5DE5 /* RStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RStore.swift; sourceTree = ""; }; + 8234BE431EF9887D009A5DE5 /* RUser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RUser.swift; sourceTree = ""; }; + 8234BE451EF9887D009A5DE5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 8234BE471EF9887D009A5DE5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; + 8234BE481EF9887D009A5DE5 /* Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "Bridging-Header.h"; sourceTree = ""; }; + 8234BE4A1EF9887D009A5DE5 /* ApiClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApiClient.swift; sourceTree = ""; }; + 8234BE4B1EF9887D009A5DE5 /* AppRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppRouter.swift; sourceTree = ""; }; + 8234BE4C1EF9887D009A5DE5 /* OAuthHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OAuthHandler.swift; sourceTree = ""; }; + 8234BE511EF9887D009A5DE5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 8234BE551EF9887D009A5DE5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 8234BE0A1EF987C4009A5DE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 6C7798886244F4E294474CFD /* Pods_BaseMVC.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 0666A70D43DF4ECED1E37C4A /* Pods */ = { + isa = PBXGroup; + children = ( + 6E818EC5E2DBA2392086830C /* Pods-BaseMVC.debug.xcconfig */, + 21D61AFBDF31D90808DB8DB3 /* Pods-BaseMVC.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; + 8234BE041EF987C4009A5DE5 = { + isa = PBXGroup; + children = ( + 8234BE0F1EF987C4009A5DE5 /* BaseMVC */, + 8234BE0E1EF987C4009A5DE5 /* Products */, + 0666A70D43DF4ECED1E37C4A /* Pods */, + 965A82F0D735A87E4EB837C7 /* Frameworks */, + ); + sourceTree = ""; + }; + 8234BE0E1EF987C4009A5DE5 /* Products */ = { + isa = PBXGroup; + children = ( + 8234BE0D1EF987C4009A5DE5 /* BaseMVC.app */, + ); + name = Products; + sourceTree = ""; + }; + 8234BE0F1EF987C4009A5DE5 /* BaseMVC */ = { + isa = PBXGroup; + children = ( + 8234BE221EF9887D009A5DE5 /* Sources */, + 8234BE1C1EF987C4009A5DE5 /* Info.plist */, + ); + path = BaseMVC; + sourceTree = ""; + }; + 8234BE221EF9887D009A5DE5 /* Sources */ = { + isa = PBXGroup; + children = ( + 8234BE231EF9887D009A5DE5 /* AppDelegate.swift */, + 8234BE441EF9887D009A5DE5 /* Resources */, + 8234BE331EF9887D009A5DE5 /* Helpers */, + 8234BE2C1EF9887D009A5DE5 /* Extensions */, + 8234BE3C1EF9887D009A5DE5 /* Libraries */, + 8234BE3D1EF9887D009A5DE5 /* Manager */, + 8234BE401EF9887D009A5DE5 /* Models */, + 8234BE491EF9887D009A5DE5 /* Services */, + 8234BE4D1EF9887D009A5DE5 /* Views */, + 8234BE241EF9887D009A5DE5 /* Controllers */, + ); + path = Sources; + sourceTree = ""; + }; + 8234BE241EF9887D009A5DE5 /* Controllers */ = { + isa = PBXGroup; + children = ( + 82F485B01EF98C81008D82A9 /* Main */, + ); + path = Controllers; + sourceTree = ""; + }; + 8234BE2C1EF9887D009A5DE5 /* Extensions */ = { + isa = PBXGroup; + children = ( + 8234BE2D1EF9887D009A5DE5 /* Color+Ext.swift */, + 8234BE2E1EF9887D009A5DE5 /* Date+Ext.swift */, + 8234BE2F1EF9887D009A5DE5 /* String+Ext.swift */, + 8234BE301EF9887D009A5DE5 /* UITableView+Ext.swift */, + 8234BE311EF9887D009A5DE5 /* UIView+Ext.swift */, + 8234BE321EF9887D009A5DE5 /* UIViewController+Ext.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + 8234BE331EF9887D009A5DE5 /* Helpers */ = { + isa = PBXGroup; + children = ( + 8234BE341EF9887D009A5DE5 /* Config.swift */, + 8234BE351EF9887D009A5DE5 /* Constants.swift */, + 8234BE361EF9887D009A5DE5 /* EnumDefine.swift */, + 8234BE371EF9887D009A5DE5 /* Func.swift */, + 8234BE381EF9887D009A5DE5 /* Keys.swift */, + 8234BE391EF9887D009A5DE5 /* LocalizableString.swift */, + 8234BE3A1EF9887D009A5DE5 /* StructDefine.swift */, + 8234BE3B1EF9887D009A5DE5 /* ViewDefine.swift */, + ); + path = Helpers; + sourceTree = ""; + }; + 8234BE3C1EF9887D009A5DE5 /* Libraries */ = { + isa = PBXGroup; + children = ( + ); + path = Libraries; + sourceTree = ""; + }; + 8234BE3D1EF9887D009A5DE5 /* Manager */ = { + isa = PBXGroup; + children = ( + 8234BE3E1EF9887D009A5DE5 /* Animator.swift */, + 8234BE3F1EF9887D009A5DE5 /* RealmManager.swift */, + ); + path = Manager; + sourceTree = ""; + }; + 8234BE401EF9887D009A5DE5 /* Models */ = { + isa = PBXGroup; + children = ( + 8234BE421EF9887D009A5DE5 /* RStore.swift */, + 8234BE431EF9887D009A5DE5 /* RUser.swift */, + ); + path = Models; + sourceTree = ""; + }; + 8234BE441EF9887D009A5DE5 /* Resources */ = { + isa = PBXGroup; + children = ( + 8234BE451EF9887D009A5DE5 /* Assets.xcassets */, + 8234BE461EF9887D009A5DE5 /* Localizable.strings */, + 8234BE481EF9887D009A5DE5 /* Bridging-Header.h */, + ); + path = Resources; + sourceTree = ""; + }; + 8234BE491EF9887D009A5DE5 /* Services */ = { + isa = PBXGroup; + children = ( + 8234BE4A1EF9887D009A5DE5 /* ApiClient.swift */, + 8234BE4B1EF9887D009A5DE5 /* AppRouter.swift */, + 8234BE4C1EF9887D009A5DE5 /* OAuthHandler.swift */, + ); + path = Services; + sourceTree = ""; + }; + 8234BE4D1EF9887D009A5DE5 /* Views */ = { + isa = PBXGroup; + children = ( + 8234BE501EF9887D009A5DE5 /* LaunchScreen.storyboard */, + 8234BE541EF9887D009A5DE5 /* Main.storyboard */, + ); + path = Views; + sourceTree = ""; + }; + 82F485B01EF98C81008D82A9 /* Main */ = { + isa = PBXGroup; + children = ( + 8234BE251EF9887D009A5DE5 /* BaseViewController.swift */, + 8234BE2A1EF9887D009A5DE5 /* StartViewController.swift */, + ); + name = Main; + sourceTree = ""; + }; + 965A82F0D735A87E4EB837C7 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 581DCC4B99E729E5223C5E6F /* Pods_BaseMVC.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 8234BE0C1EF987C4009A5DE5 /* BaseMVC */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8234BE1F1EF987C4009A5DE5 /* Build configuration list for PBXNativeTarget "BaseMVC" */; + buildPhases = ( + EE1A350C9EA0B2E22EEEAD20 /* [CP] Check Pods Manifest.lock */, + 8234BE091EF987C4009A5DE5 /* Sources */, + 8234BE0A1EF987C4009A5DE5 /* Frameworks */, + 8234BE0B1EF987C4009A5DE5 /* Resources */, + 82CFF1F22C619A66AB2BD605 /* [CP] Embed Pods Frameworks */, + 8453F4E3F802CF6A851561F5 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = BaseMVC; + productName = BaseMVC; + productReference = 8234BE0D1EF987C4009A5DE5 /* BaseMVC.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 8234BE051EF987C4009A5DE5 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0830; + LastUpgradeCheck = 0830; + ORGANIZATIONNAME = THL; + TargetAttributes = { + 8234BE0C1EF987C4009A5DE5 = { + CreatedOnToolsVersion = 8.3.3; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 8234BE081EF987C4009A5DE5 /* Build configuration list for PBXProject "BaseMVC" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 8234BE041EF987C4009A5DE5; + productRefGroup = 8234BE0E1EF987C4009A5DE5 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 8234BE0C1EF987C4009A5DE5 /* BaseMVC */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 8234BE0B1EF987C4009A5DE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8234BE801EF9887D009A5DE5 /* Assets.xcassets in Resources */, + 8234BE861EF9887D009A5DE5 /* LaunchScreen.storyboard in Resources */, + 8234BE881EF9887D009A5DE5 /* Main.storyboard in Resources */, + 8234BE811EF9887D009A5DE5 /* Localizable.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 82CFF1F22C619A66AB2BD605 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-BaseMVC/Pods-BaseMVC-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 8453F4E3F802CF6A851561F5 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-BaseMVC/Pods-BaseMVC-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + EE1A350C9EA0B2E22EEEAD20 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 8234BE091EF987C4009A5DE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8234BE6B1EF9887D009A5DE5 /* StartViewController.swift in Sources */, + 8234BE731EF9887D009A5DE5 /* Config.swift in Sources */, + 8234BE7F1EF9887D009A5DE5 /* RUser.swift in Sources */, + 8234BE791EF9887D009A5DE5 /* StructDefine.swift in Sources */, + 8234BE781EF9887D009A5DE5 /* LocalizableString.swift in Sources */, + 8234BE821EF9887D009A5DE5 /* ApiClient.swift in Sources */, + 8234BE761EF9887D009A5DE5 /* Func.swift in Sources */, + 8234BE721EF9887D009A5DE5 /* UIViewController+Ext.swift in Sources */, + 8234BE751EF9887D009A5DE5 /* EnumDefine.swift in Sources */, + 8234BE771EF9887D009A5DE5 /* Keys.swift in Sources */, + 8234BE6D1EF9887D009A5DE5 /* Color+Ext.swift in Sources */, + 8234BE651EF9887D009A5DE5 /* AppDelegate.swift in Sources */, + 8234BE841EF9887D009A5DE5 /* OAuthHandler.swift in Sources */, + 8234BE741EF9887D009A5DE5 /* Constants.swift in Sources */, + 8234BE661EF9887D009A5DE5 /* BaseViewController.swift in Sources */, + 8234BE7E1EF9887D009A5DE5 /* RStore.swift in Sources */, + 8234BE7C1EF9887D009A5DE5 /* RealmManager.swift in Sources */, + 8234BE711EF9887D009A5DE5 /* UIView+Ext.swift in Sources */, + 8234BE701EF9887D009A5DE5 /* UITableView+Ext.swift in Sources */, + 8234BE6E1EF9887D009A5DE5 /* Date+Ext.swift in Sources */, + 8234BE6F1EF9887D009A5DE5 /* String+Ext.swift in Sources */, + 8234BE7B1EF9887D009A5DE5 /* Animator.swift in Sources */, + 8234BE831EF9887D009A5DE5 /* AppRouter.swift in Sources */, + 8234BE7A1EF9887D009A5DE5 /* ViewDefine.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 8234BE461EF9887D009A5DE5 /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + 8234BE471EF9887D009A5DE5 /* Base */, + ); + name = Localizable.strings; + sourceTree = ""; + }; + 8234BE501EF9887D009A5DE5 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 8234BE511EF9887D009A5DE5 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; + 8234BE541EF9887D009A5DE5 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 8234BE551EF9887D009A5DE5 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 8234BE1D1EF987C4009A5DE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.1; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 8234BE1E1EF987C4009A5DE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.1; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 8234BE201EF987C4009A5DE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 6E818EC5E2DBA2392086830C /* Pods-BaseMVC.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = BaseMVC/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = thl.mvc.vn; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 3.0; + }; + name = Debug; + }; + 8234BE211EF987C4009A5DE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 21D61AFBDF31D90808DB8DB3 /* Pods-BaseMVC.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = BaseMVC/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = thl.mvc.vn; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 3.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 8234BE081EF987C4009A5DE5 /* Build configuration list for PBXProject "BaseMVC" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8234BE1D1EF987C4009A5DE5 /* Debug */, + 8234BE1E1EF987C4009A5DE5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 8234BE1F1EF987C4009A5DE5 /* Build configuration list for PBXNativeTarget "BaseMVC" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8234BE201EF987C4009A5DE5 /* Debug */, + 8234BE211EF987C4009A5DE5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 8234BE051EF987C4009A5DE5 /* Project object */; +} diff --git a/BaseMVC.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/BaseMVC.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..e8bad37 --- /dev/null +++ b/BaseMVC.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/BaseMVC.xcworkspace/contents.xcworkspacedata b/BaseMVC.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..4d8e0a7 --- /dev/null +++ b/BaseMVC.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/BaseMVC/Info.plist b/BaseMVC/Info.plist new file mode 100644 index 0000000..93b96dc --- /dev/null +++ b/BaseMVC/Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + + diff --git a/BaseMVC/Sources/AppDelegate.swift b/BaseMVC/Sources/AppDelegate.swift new file mode 100644 index 0000000..8fa54cb --- /dev/null +++ b/BaseMVC/Sources/AppDelegate.swift @@ -0,0 +1,39 @@ +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + + // config realm + RealmManager.config() + + // return + return true + } + + func applicationWillResignActive(_ application: UIApplication) { + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. + } + + func applicationDidEnterBackground(_ application: UIApplication) { + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. + } + + func applicationWillEnterForeground(_ application: UIApplication) { + // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. + } + + func applicationDidBecomeActive(_ application: UIApplication) { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. + } + + func applicationWillTerminate(_ application: UIApplication) { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. + } + +} diff --git a/BaseMVC/Sources/Controllers/BaseViewController.swift b/BaseMVC/Sources/Controllers/BaseViewController.swift new file mode 100644 index 0000000..8c17156 --- /dev/null +++ b/BaseMVC/Sources/Controllers/BaseViewController.swift @@ -0,0 +1,118 @@ +import UIKit + +class BaseViewController: UIViewController, UIGestureRecognizerDelegate { + + // MARK: - IBOutlet + + // MARK: - Varialbes + + // MARK: - View Lifecycle + override func viewDidLoad() { + super.viewDidLoad() + + self.navigationController?.interactivePopGestureRecognizer?.delegate = self + self.createBackMenuButton() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + if let count = self.navigationController?.viewControllers.count, + count > 1 { + self.enableSwipeBack(enable: true) + } else { + self.enableSwipeBack(enable: false) + } + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + } + + override func didReceiveMemoryWarning() { + super.didReceiveMemoryWarning() + // Dispose of any resources that can be recreated. + } + + // MARK: - Navigation + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + // Get the new view controller using segue.destinationViewController. + // Pass the selected object to the new view controller. + } + + // MARK: - Setup View + private func createBackMenuButton() { + + if (self.navigationController?.viewControllers.count ?? 0) > 1 { + // add back + self.addBtnBackNav() + } else { + self.navigationItem.leftBarButtonItem = nil + self.navigationItem.hidesBackButton = true + } + } + + func enableSwipeBack(enable: Bool) { + self.navigationController?.interactivePopGestureRecognizer?.isEnabled = enable + } + + func setNavTitle(title: String?) { + self.navigationItem.title = title + } + + func addBtnBackNav(tinColor: UIColor? = nil) { + let backButton = self.createBtnNavWithImage(image: #imageLiteral(resourceName: "ic_arrow_back"), target: self, action: #selector(BaseViewController.actionTouchBtnBack), tinColor: tinColor) + self.navigationItem.setLeftBarButton(UIBarButtonItem(customView: backButton), animated: false) + } + + func addBtnLeftNavWithImage(image: UIImage?, tinColor: UIColor? = nil) { + let leftButton = self.createBtnNavWithImage(image: image, target: self, action: #selector(BaseViewController.actionTouchBtnLeft), tinColor: tinColor) + self.navigationItem.setLeftBarButton(UIBarButtonItem(customView: leftButton), animated: false) + } + + func addBtnRightNavWithImage(image: UIImage?, tinColor: UIColor? = nil) { + let rightButton = self.createBtnNavWithImage(image: image, target: self, action: #selector(BaseViewController.actionTouchBtnRight), tinColor: tinColor) + self.navigationItem.setRightBarButton(UIBarButtonItem(customView: rightButton), animated: false) + } + + func addBtnLeftNavWithTitle(title: String?) { + if let title = title { + let leftNavBtn = UIBarButtonItem(title: title, style: .plain, target: self, action: #selector(BaseViewController.actionTouchBtnLeft)) + self.navigationItem.leftBarButtonItem = leftNavBtn + } + } + + func addBtnRightNavWithTitle(title: String?) { + let rightNavBtn = UIBarButtonItem(title: title, style: .plain, target: self, action: #selector(BaseViewController.actionTouchBtnRight)) + self.navigationItem.rightBarButtonItem = rightNavBtn + } + + private func createBtnNavWithImage(image: UIImage?, target: AnyObject?, action: Selector, tinColor: UIColor? = nil) -> UIButton { + let navButton: UIButton = UIButton(type: UIButtonType.system) + navButton.addTarget(target, action: action, for: UIControlEvents.touchUpInside) + navButton.frame = CGRect(x: 0, y: 0, width: 20, height: 20) + navButton.setImage(image, for: UIControlState.normal) + navButton.tintColor = tinColor ?? UIColor.black + return navButton + } + + // MARK: - Actions + func actionTouchBtnBack() { + _ = self.navigationController?.popViewController(animated: true) + } + + func actionTouchBtnLeft() { + } + + func actionTouchBtnRight() { + } + + // MARK: - Call Api + + // MARK: - Functions + +} diff --git a/BaseMVC/Sources/Controllers/StartViewController.swift b/BaseMVC/Sources/Controllers/StartViewController.swift new file mode 100644 index 0000000..9a2d3c5 --- /dev/null +++ b/BaseMVC/Sources/Controllers/StartViewController.swift @@ -0,0 +1,55 @@ +import UIKit + +class StartViewController: BaseViewController { + + // MARK: - IBOutlet + + @IBOutlet weak var tableView: UITableView! + + // MARK: - Varialbes + + // MARK: - View Lifecycle + override func viewDidLoad() { + super.viewDidLoad() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + // check app + self.checkApp() + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + } + + override func didReceiveMemoryWarning() { + super.didReceiveMemoryWarning() + // Dispose of any resources that can be recreated. + } + + // MARK: - Navigation + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + // Get the new view controller using segue.destinationViewController. + // Pass the selected object to the new view controller. + } + + // MARK: - Setup View + + // MARK: - Actions + + // MARK: - Call Api + + // MARK: - Functions + private func checkApp() { + } + + func gotoMainApp() { + + } +} diff --git a/BaseMVC/Sources/Extensions/Color+Ext.swift b/BaseMVC/Sources/Extensions/Color+Ext.swift new file mode 100644 index 0000000..b6a0d30 --- /dev/null +++ b/BaseMVC/Sources/Extensions/Color+Ext.swift @@ -0,0 +1,29 @@ +import UIKit + +extension UIColor { + + static func colorWith(red: Float, green: Float, blue: Float) -> UIColor { + return UIColor(colorLiteralRed: red / Float(255.0), green: green / Float(255.0), blue: blue / Float(255.0), alpha: 1.0) + } + + convenience init(hex: String) { + let scanner = Scanner(string: hex) + scanner.scanLocation = 0 + var rgbValue: UInt64 = 0 + scanner.scanHexInt64(&rgbValue) + let r = (rgbValue & 0xff0000) >> 16 + let g = (rgbValue & 0xff00) >> 8 + let b = rgbValue & 0xff + self.init(red: CGFloat(r) / 0xff, green: CGFloat(g) / 0xff, blue: CGFloat(b) / 0xff, alpha: 1.0) + } + + var toHexString: String { + var r: CGFloat = 0 + var g: CGFloat = 0 + var b: CGFloat = 0 + var a: CGFloat = 0 + self.getRed(&r, green: &g, blue: &b, alpha: &a) + // return + return String(format: "%02X%02X%02X", Int(r * 0xff), Int(g * 0xff), Int(b * 0xff)) + } +} diff --git a/BaseMVC/Sources/Extensions/Date+Ext.swift b/BaseMVC/Sources/Extensions/Date+Ext.swift new file mode 100644 index 0000000..eaf99b7 --- /dev/null +++ b/BaseMVC/Sources/Extensions/Date+Ext.swift @@ -0,0 +1,58 @@ +import Foundation + +//class Formatter: NSObject { +// static let shared = Formatter() +// +// let dateFormatter = DateFormatter() +// +// private override init() { +// super.init() +// dateFormatter.timeZone = NSTimeZone.default +// dateFormatter.locale = NSLocale(localeIdentifier: Constant.defaultDateLocale) as Locale! +// } +//} + +extension Date { + + // MARK: - Date + static func convertDateToString(fromDate: Date?, format: String) -> String? { + if let convertDate = fromDate { + let dateFormatter = DateFormatter() + dateFormatter.timeZone = NSTimeZone.default + dateFormatter.locale = NSLocale(localeIdentifier: Constant.defaultDateLocale) as Locale! + dateFormatter.dateFormat = format + return dateFormatter.string(from: convertDate) + } else { + return nil + } + } + + static func convertStringToDate(fromString: String?, format: String) -> Date? { + if let dateString = fromString { + let dateFormatter = DateFormatter() + dateFormatter.timeZone = NSTimeZone.default + dateFormatter.locale = NSLocale(localeIdentifier: Constant.defaultDateLocale) as Locale! + dateFormatter.dateFormat = format + return dateFormatter.date(from: dateString) + } else { + return nil + } + } + + static func convertStringDateToString(fromString: String?, fromFormat: String, toFormat: String) -> String? { + if let dateString = fromString { + let dateFormatter = DateFormatter() + dateFormatter.timeZone = NSTimeZone.default + dateFormatter.locale = NSLocale(localeIdentifier: Constant.defaultDateLocale) as Locale! + dateFormatter.dateFormat = fromFormat + guard let date = dateFormatter.date(from: dateString) else { + return nil + } + dateFormatter.dateFormat = toFormat + return dateFormatter.string(from: date) + } else { + return nil + } + } + +} diff --git a/BaseMVC/Sources/Extensions/String+Ext.swift b/BaseMVC/Sources/Extensions/String+Ext.swift new file mode 100644 index 0000000..7a227c3 --- /dev/null +++ b/BaseMVC/Sources/Extensions/String+Ext.swift @@ -0,0 +1,12 @@ +import Foundation + +extension String { + + var localized: String { + return self.localizedWithComment(comment: "") + } + + func localizedWithComment(comment: String) -> String { + return NSLocalizedString(self, comment: comment) + } +} diff --git a/BaseMVC/Sources/Extensions/UITableView+Ext.swift b/BaseMVC/Sources/Extensions/UITableView+Ext.swift new file mode 100644 index 0000000..f5db342 --- /dev/null +++ b/BaseMVC/Sources/Extensions/UITableView+Ext.swift @@ -0,0 +1,16 @@ +import UIKit + +extension UITableView { + + func registerNibCellBy(indentifier: String) { + self.register(UINib(nibName: indentifier, bundle: nil), forCellReuseIdentifier: indentifier) + } + + func registerNibHeaderViewBy(indentifier: String) { + self.register(UINib(nibName: indentifier, bundle: nil), forHeaderFooterViewReuseIdentifier: indentifier) + } + + func hideEmptyCells() { + self.tableFooterView = UIView(frame: .zero) + } +} diff --git a/BaseMVC/Sources/Extensions/UIView+Ext.swift b/BaseMVC/Sources/Extensions/UIView+Ext.swift new file mode 100644 index 0000000..e45952a --- /dev/null +++ b/BaseMVC/Sources/Extensions/UIView+Ext.swift @@ -0,0 +1,8 @@ +import UIKit + +extension UIView { + + class func fromNib() -> T? { + return Bundle.main.loadNibNamed(String(describing: self), owner: nil, options: nil)?.first as? T + } +} diff --git a/BaseMVC/Sources/Extensions/UIViewController+Ext.swift b/BaseMVC/Sources/Extensions/UIViewController+Ext.swift new file mode 100644 index 0000000..2a22c1a --- /dev/null +++ b/BaseMVC/Sources/Extensions/UIViewController+Ext.swift @@ -0,0 +1,30 @@ +import UIKit + +// MARK: UIViewController +extension UIViewController { + + static func getViewControllerFromStoryboard(_ storyboardName: String) -> UIViewController { + let identifier = String(describing: self) + return self.getViewControllerWithIdentifier(identifier, storyboardName: storyboardName) + } + + static func getNavigationControllerFromStoryboard(_ storyboardName: String) -> UIViewController { + let identifier = String(describing: self) + "Nav" + return self.getViewControllerWithIdentifier(identifier, storyboardName: storyboardName) + } + + static func getViewControllerWithIdentifier(_ identifier: String, storyboardName: String) -> UIViewController { + let storyboard = UIStoryboard(name: storyboardName, bundle: nil) + return storyboard.instantiateViewController(withIdentifier: identifier) + } +} + +extension UIViewController { + func showAlertWith(title: String?, message: String?, titleDefault: String?, handlerDefault: ((UIAlertAction) -> Swift.Void)? = nil) { + // alert + let alertVC = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert) + alertVC.addAction(UIAlertAction(title: titleDefault, style: UIAlertActionStyle.default, handler: handlerDefault)) + // present + self.present(alertVC, animated: true, completion: nil) + } +} diff --git a/BaseMVC/Sources/Helpers/Config.swift b/BaseMVC/Sources/Helpers/Config.swift new file mode 100644 index 0000000..dd9117b --- /dev/null +++ b/BaseMVC/Sources/Helpers/Config.swift @@ -0,0 +1,6 @@ +let baseUrl = "https://dev.mvc.vn" +let clientId = "MVC" +let clientSecrect = "secret" +let dataVersion = 1 +let appId = "12345678" +let platform = "iOS" diff --git a/BaseMVC/Sources/Helpers/Constants.swift b/BaseMVC/Sources/Helpers/Constants.swift new file mode 100644 index 0000000..e0eef0b --- /dev/null +++ b/BaseMVC/Sources/Helpers/Constants.swift @@ -0,0 +1,5 @@ +struct Constant { + static let itunesAppURL = "itms-apps://itunes.apple.com/app/id" + static let errorDomain = "ErrorDomain" + static let defaultDateLocale = "en_US_POSIX" +} diff --git a/BaseMVC/Sources/Helpers/EnumDefine.swift b/BaseMVC/Sources/Helpers/EnumDefine.swift new file mode 100644 index 0000000..8337712 --- /dev/null +++ b/BaseMVC/Sources/Helpers/EnumDefine.swift @@ -0,0 +1 @@ +// diff --git a/BaseMVC/Sources/Helpers/Func.swift b/BaseMVC/Sources/Helpers/Func.swift new file mode 100644 index 0000000..6d91381 --- /dev/null +++ b/BaseMVC/Sources/Helpers/Func.swift @@ -0,0 +1,12 @@ +import Foundation + +let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] + +func logD(_ message: String, function: String = #function) { + #if !NDEBUG + let formatter = DateFormatter() + formatter.dateFormat = "HH:mm:ss" + let date = formatter.string(from: NSDate() as Date) + print("\(date) Func: \(function) : \(message)") + #endif +} diff --git a/BaseMVC/Sources/Helpers/Keys.swift b/BaseMVC/Sources/Helpers/Keys.swift new file mode 100644 index 0000000..72b61f4 --- /dev/null +++ b/BaseMVC/Sources/Helpers/Keys.swift @@ -0,0 +1,14 @@ +struct ParamKey { + static let clientId = "client_id" + static let clientSecret = "client_secret" + static let grantType = "grant_type" + static let refreshToken = "refresh_token" + static let accessToken = "access_token" +} + +struct JSONKey { + static let id = "id" + static let name = "name" + static let refreshToken = "refresh_token" + static let accessToken = "access_token" +} diff --git a/BaseMVC/Sources/Helpers/LocalizableString.swift b/BaseMVC/Sources/Helpers/LocalizableString.swift new file mode 100644 index 0000000..1f0295f --- /dev/null +++ b/BaseMVC/Sources/Helpers/LocalizableString.swift @@ -0,0 +1,4 @@ +struct LocalizedString { + static let logIn = "logIn" + static let logOut = "logOut" +} diff --git a/BaseMVC/Sources/Helpers/StructDefine.swift b/BaseMVC/Sources/Helpers/StructDefine.swift new file mode 100644 index 0000000..f09bcc1 --- /dev/null +++ b/BaseMVC/Sources/Helpers/StructDefine.swift @@ -0,0 +1,17 @@ +struct DateFormat { + static let yyyyssDash = "yyyy-MM-dd'T'HH:mm:ss" + static let ddmmSlash = "dd/MM/yyyy HH:mm" + static let ddMMyyyy = "dd/MM/yyyy" + static let yyyyMMddDash = "yyyy-MM-dd" + static let MMyyyy = "MM/yyyy" +} + +public struct GroupItem { + public var group: Group + public var items: [Item] + + public init(group: Group, items: [Item]) { + self.group = group + self.items = items + } +} diff --git a/BaseMVC/Sources/Helpers/ViewDefine.swift b/BaseMVC/Sources/Helpers/ViewDefine.swift new file mode 100644 index 0000000..863aac6 --- /dev/null +++ b/BaseMVC/Sources/Helpers/ViewDefine.swift @@ -0,0 +1,16 @@ +// MARK: - Storyboard +struct Storyboard { + + struct Main { + static let name = "Main" + static let startViewController = "StartViewController" + } +} + +struct ReuseView { + static let leftMenuCell = "LeftMenuCell" +} + +struct SegueIdentifier { + static let gotoMainApp = "gotoMainApp" +} diff --git a/BaseMVC/Sources/Manager/Animator.swift b/BaseMVC/Sources/Manager/Animator.swift new file mode 100644 index 0000000..0fe653f --- /dev/null +++ b/BaseMVC/Sources/Manager/Animator.swift @@ -0,0 +1,102 @@ +import UIKit + +public enum AnimationType { + case push + case pop + case present + case dismiss + case none +} + +class Animator: NSObject, UIViewControllerAnimatedTransitioning { + + var presentedType: AnimationType = .push + var dismissedType: AnimationType = .pop + var animateType: AnimationType = .none + var transitionDuration: TimeInterval = 0.33 + + // MARK: Init + override init() { + + } + + init(presentedType: AnimationType, dismissedType: AnimationType) { + self.presentedType = presentedType + self.dismissedType = dismissedType + } + + func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { + return transitionDuration + } + + func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { + switch animateType { + case .push: + self.pushAnimation(transitionContext) + case .pop: + self.popAnimation(transitionContext) + default: + self.pushAnimation(transitionContext) + } + } + + private func pushAnimation(_ transitionContext: UIViewControllerContextTransitioning) { + + // get viewControllers + let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)! + let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)! + let finalFrameForVC = transitionContext.finalFrame(for: toViewController) + let containerView = transitionContext.containerView + + // set frame + let bounds = UIScreen.main.bounds + toViewController.view.frame = finalFrameForVC.offsetBy(dx: bounds.size.width, dy: 0) + toViewController.view.clipsToBounds = true + containerView.addSubview(toViewController.view) + + // animate + UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0.0, options: .curveLinear, animations: { + + fromViewController.view.alpha = 0.5 + toViewController.view.frame = finalFrameForVC + + }) { (_ ) in + transitionContext.completeTransition(true) + fromViewController.view.alpha = 1.0 + } + } + + private func popAnimation(_ transitionContext: UIViewControllerContextTransitioning) { + + // get viewControllers + let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)! + let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)! + let finalFrameForVC = transitionContext.finalFrame(for: toViewController) + let containerView = transitionContext.containerView + + // set frame + toViewController.view.frame = finalFrameForVC + toViewController.view.alpha = 0.5 + containerView.addSubview(toViewController.view) + containerView.sendSubview(toBack: toViewController.view) + + // create view by snapshot + let snapshotView = fromViewController.view.snapshotView(afterScreenUpdates: false) + snapshotView?.frame = fromViewController.view.frame + containerView.addSubview(snapshotView!) + + fromViewController.view.removeFromSuperview() + + // animate + UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: { + let bounds = UIScreen.main.bounds + snapshotView?.frame = finalFrameForVC.offsetBy(dx: bounds.size.width, dy: 0) + + toViewController.view.alpha = 1.0 + + }, completion: { _ in + snapshotView?.removeFromSuperview() + transitionContext.completeTransition(true) + }) + } +} diff --git a/BaseMVC/Sources/Manager/RealmManager.swift b/BaseMVC/Sources/Manager/RealmManager.swift new file mode 100644 index 0000000..7e1d1cc --- /dev/null +++ b/BaseMVC/Sources/Manager/RealmManager.swift @@ -0,0 +1,23 @@ +import RealmSwift + +struct RealmManager { + + static func config() { + let fileURL = Realm.Configuration.defaultConfiguration.fileURL + logD("\(String(describing: fileURL))") + + // get schema version + let schemaVersion = UInt64(dataVersion) + + // create config + let config = Realm.Configuration( + schemaVersion: schemaVersion, + migrationBlock: { _, oldSchemaVersion in + if oldSchemaVersion < schemaVersion { + } + }) + + // set config + Realm.Configuration.defaultConfiguration = config + } +} diff --git a/BaseMVC/Sources/Models/RStore.swift b/BaseMVC/Sources/Models/RStore.swift new file mode 100644 index 0000000..e3b193c --- /dev/null +++ b/BaseMVC/Sources/Models/RStore.swift @@ -0,0 +1,16 @@ +import Foundation +import RealmSwift + +class RStore: Object { + + dynamic var id: String? + + dynamic var name: String? + + let user = LinkingObjects(fromType: RUser.self, property: "store") + + override static func primaryKey() -> String? { + return "id" + } + +} diff --git a/BaseMVC/Sources/Models/RUser.swift b/BaseMVC/Sources/Models/RUser.swift new file mode 100644 index 0000000..5bdea49 --- /dev/null +++ b/BaseMVC/Sources/Models/RUser.swift @@ -0,0 +1,24 @@ +import Foundation +import RealmSwift + +class RUser: Object { + + dynamic var id: String? + + dynamic var userName: String? + + let height = RealmOptional() + + dynamic var email: String? + + dynamic var accessToken: String? + + let isCurrent = RealmOptional() + + dynamic var store: RStore? + + override static func primaryKey() -> String? { + return "id" + } + +} diff --git a/BaseMVC/Sources/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/BaseMVC/Sources/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..b8236c6 --- /dev/null +++ b/BaseMVC/Sources/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,48 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/BaseMVC/Sources/Resources/Assets.xcassets/Contents.json b/BaseMVC/Sources/Resources/Assets.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/BaseMVC/Sources/Resources/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/BaseMVC/Sources/Resources/Assets.xcassets/ic_arrow_back.imageset/Contents.json b/BaseMVC/Sources/Resources/Assets.xcassets/ic_arrow_back.imageset/Contents.json new file mode 100644 index 0000000..0fe30f4 --- /dev/null +++ b/BaseMVC/Sources/Resources/Assets.xcassets/ic_arrow_back.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_arrow_back@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/BaseMVC/Sources/Resources/Assets.xcassets/ic_arrow_back.imageset/ic_arrow_back@2x.png b/BaseMVC/Sources/Resources/Assets.xcassets/ic_arrow_back.imageset/ic_arrow_back@2x.png new file mode 100644 index 0000000..6842725 Binary files /dev/null and b/BaseMVC/Sources/Resources/Assets.xcassets/ic_arrow_back.imageset/ic_arrow_back@2x.png differ diff --git a/BaseMVC/Sources/Resources/Base.lproj/Localizable.strings b/BaseMVC/Sources/Resources/Base.lproj/Localizable.strings new file mode 100644 index 0000000..acd647b --- /dev/null +++ b/BaseMVC/Sources/Resources/Base.lproj/Localizable.strings @@ -0,0 +1,2 @@ +logIn = "Login"; +logOut = "Log Out"; diff --git a/BaseMVC/Sources/Resources/Bridging-Header.h b/BaseMVC/Sources/Resources/Bridging-Header.h new file mode 100644 index 0000000..8337712 --- /dev/null +++ b/BaseMVC/Sources/Resources/Bridging-Header.h @@ -0,0 +1 @@ +// diff --git a/BaseMVC/Sources/Services/ApiClient.swift b/BaseMVC/Sources/Services/ApiClient.swift new file mode 100644 index 0000000..c120776 --- /dev/null +++ b/BaseMVC/Sources/Services/ApiClient.swift @@ -0,0 +1,148 @@ +import Alamofire + +public typealias ResponseHandler = (ResponseObject?) -> Void + +struct HeaderKey { + static let ContentType = "Content-Type" + static let Authorization = "Authorization" + static let Accept = "Accept" +} + +struct HeaderValue { + static let ApplicationJson = "application/json" + static let ApplicationOctetStream = "application/octet-stream" + static let ApplicationXWWWFormUrlencoded = "application/x-www-form-urlencoded" +} + +enum RequestResult { + case success + case error + // case timeOut + // case notConnectedToInternet + case cancelled +} + +public enum HttpStatusCode: Int { + case ok = 200 + case badRequest = 400 + case unauthorized = 401 + case notFound = 404 + case conflict = 409 + case serviceUnavailable = 503 + case notConnectedToInternet = -1009 + case cancelled = -999 + case timeOut = -1001 + case cannotFindHost = -1003 + + init?(statusCode: Int?) { + guard let _statusCode = statusCode else { + return nil + } + self.init(rawValue: _statusCode) + } +} + +public struct ResponseObject { + let data: AnyObject? + let statusCode: HttpStatusCode? // code error, incase success + let result: RequestResult +} + +struct ApiClient { + + private static let defaultEncoding = JSONEncoding.default + + private static let defaultSessionManager: SessionManager = { + + // defaultHeaders + var defaultHeaders = SessionManager.defaultHTTPHeaders + defaultHeaders[HeaderKey.Accept] = HeaderValue.ApplicationJson + + // configuration + let configuration = URLSessionConfiguration.default + configuration.httpAdditionalHeaders = defaultHeaders + configuration.timeoutIntervalForRequest = 20 + + // sessionManager + let sessionManager = SessionManager(configuration: configuration) + + // OAuthHandler + let oAuthHandler = OAuthHandler(baseUrl: baseUrl) + sessionManager.adapter = oAuthHandler + sessionManager.retrier = oAuthHandler + + return sessionManager + }() + + // MARK: - Functions + private static func analyzeResponse(response: DataResponse, completionHandler: ResponseHandler?) { + debugPrint(response) + + // http code + let httpStatusCode = HttpStatusCode(statusCode: response.response?.statusCode) + + switch response.result { + case .success(let value): + ApiClient.successWithValue(data: value as AnyObject, httpStatusCode: httpStatusCode, completionHandler: completionHandler) + + case .failure(let error): + ApiClient.failureWithError(error: error, data: response.data, httpStatusCode: httpStatusCode, completionHandler: completionHandler) + } + } + + private static func successWithValue(data: AnyObject, httpStatusCode: HttpStatusCode?, completionHandler: ResponseHandler?) { + + // create obj response + let responseObject = ResponseObject(data: data, statusCode: httpStatusCode, result: RequestResult.success) + + // block + completionHandler?(responseObject) + + } + + private static func failureWithError(error: Error?, data: Data? = nil, httpStatusCode: HttpStatusCode?, completionHandler: ResponseHandler?) { + var errorCode: HttpStatusCode? = httpStatusCode + var requestResult: RequestResult = RequestResult.error + var errorData: AnyObject? = nil + + // check error code + if error?._code == NSURLErrorTimedOut { // Time out + errorCode = HttpStatusCode(rawValue: NSURLErrorTimedOut) + requestResult = .error + } else if error?._code == NSURLErrorCancelled { // Cancelled + errorCode = HttpStatusCode(rawValue: NSURLErrorCancelled) + requestResult = .cancelled + } else if error?._code == NSURLErrorNotConnectedToInternet { // Not connected to internet + errorCode = HttpStatusCode(rawValue: NSURLErrorNotConnectedToInternet) + requestResult = .error + } else if error?._code == NSURLErrorCannotFindHost { // Can not Find Host + errorCode = HttpStatusCode(rawValue: NSURLErrorCannotFindHost) + requestResult = .error + } else { // Orther + if let _data = data { + do { + errorData = try JSONSerialization.jsonObject(with: _data, options: []) as AnyObject + } catch { + } + } + } + + // create obj response + let responseObject = ResponseObject(data: errorData, statusCode: errorCode, result: requestResult) + + // block + completionHandler?(responseObject) + } + + // MARK: - Request + static func request(urlRequest: URLRequestConvertible, completionHandler: ResponseHandler? = nil) -> Request? { + + // Request + let manager = ApiClient.defaultSessionManager + return manager.request(urlRequest).validate().responseJSON { (response) in + // analyze response + ApiClient.analyzeResponse(response: response, completionHandler: completionHandler) + } + } + +} diff --git a/BaseMVC/Sources/Services/AppRouter.swift b/BaseMVC/Sources/Services/AppRouter.swift new file mode 100644 index 0000000..ec31910 --- /dev/null +++ b/BaseMVC/Sources/Services/AppRouter.swift @@ -0,0 +1,33 @@ +import Alamofire + +enum AppRouter: URLRequestConvertible { + case getAppInfo(parameters: Parameters) + + var method: HTTPMethod { + switch self { + case .getAppInfo: + return .get + } + } + + var path: String { + switch self { + case .getAppInfo : + return "/app/infos" + } + } + // MARK: URLRequestConvertible + func asURLRequest() throws -> URLRequest { + let url = try baseUrl.asURL() + + var urlRequest = URLRequest(url: url.appendingPathComponent(path)) + urlRequest.httpMethod = method.rawValue + + switch self { + case .getAppInfo(let parameters): + urlRequest = try JSONEncoding.default.encode(urlRequest, with: parameters) + } + + return urlRequest + } +} diff --git a/BaseMVC/Sources/Services/OAuthHandler.swift b/BaseMVC/Sources/Services/OAuthHandler.swift new file mode 100644 index 0000000..8de2ee3 --- /dev/null +++ b/BaseMVC/Sources/Services/OAuthHandler.swift @@ -0,0 +1,168 @@ +import Alamofire +import SwiftyJSON + +class OAuthHandler: RequestAdapter, RequestRetrier { + + private static let keyRefreshToken: String = "OAuth_Refresh_Token" + + private static let keyAccessToken: String = "OAuth_Access_Token" + + private static let keyTokenType: String = "OAuth_Token_Type" + + private static let keyClientId: String = "OAuth_Client_Id" + + private var clientId: String? { + get { + let userDefault = UserDefaults.standard + return userDefault.string(forKey: OAuthHandler.keyClientId) + } + set { + let userDefault = UserDefaults.standard + userDefault.set(newValue, forKey: OAuthHandler.keyClientId) + } + } + + private var accessToken: String? { + get { + let userDefault = UserDefaults.standard + return userDefault.string(forKey: OAuthHandler.keyAccessToken) + } + set { + let userDefault = UserDefaults.standard + userDefault.set(newValue, forKey: OAuthHandler.keyAccessToken) + } + } + + private var refreshToken: String? { + get { + let userDefault = UserDefaults.standard + return userDefault.string(forKey: OAuthHandler.keyRefreshToken) + } + set { + let userDefault = UserDefaults.standard + userDefault.set(newValue, forKey: OAuthHandler.keyRefreshToken) + } + } + + private var tokenType: String? { + get { + let userDefault = UserDefaults.standard + return userDefault.string(forKey: OAuthHandler.keyTokenType) + } + set { + let userDefault = UserDefaults.standard + userDefault.set(newValue, forKey: OAuthHandler.keyTokenType) + } + } + + private let lock = NSLock() + + private var baseUrl: String? + + private var isRefreshing = false + + private var requestsToRetry: [RequestRetryCompletion] = [] + + private let sessionManager: SessionManager = { + let configuration = URLSessionConfiguration.default + configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders + + return SessionManager(configuration: configuration) + }() + + // MARK: - Initialization + init(baseUrl: String?) { + self.baseUrl = baseUrl + } + + func parse(jsonObject: AnyObject?) -> Bool { + + guard let jsonData = jsonObject else { return false } + + // using SwiftyJSON + let json = JSON(jsonData) + + // parse + if let accessToken = json[JSONKey.accessToken].string, + let refreshToken = json[JSONKey.refreshToken].string { + self.accessToken = accessToken + self.refreshToken = refreshToken + return true + } + + return false + } + + // MARK: - RequestAdapter + func adapt(_ urlRequest: URLRequest) throws -> URLRequest { + var urlRequest = urlRequest + + if let _accessToken = self.accessToken, + let _baseUrl = self.baseUrl, + let urlString = urlRequest.url?.absoluteString, + urlString.hasPrefix(_baseUrl) { + urlRequest.setValue("Bearer " + _accessToken, forHTTPHeaderField: HeaderKey.Authorization) + } + + return urlRequest + } + + // MARK: - RequestRetrier + func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) { + lock.lock() + + defer { + lock.unlock() + } + + if let response = request.task?.response as? HTTPURLResponse, + response.statusCode == HttpStatusCode.unauthorized.rawValue { + self.requestsToRetry.append(completion) + + if !self.isRefreshing { + + // refresh token + self.refreshTokens { [weak self] value in + guard let strongSelf = self else { return } + + strongSelf.lock.lock() + + defer { + strongSelf.lock.unlock() + } + + let success = strongSelf.parse(jsonObject: value) + + strongSelf.requestsToRetry.forEach { $0(success, 0.0) } + strongSelf.requestsToRetry.removeAll() + } + } + } else { + completion(false, 0.0) + } + } + + // MARK: - Private - Refresh Tokens + private func refreshTokens(completion: @escaping (_ value: AnyObject?) -> Void) { + guard !isRefreshing else { return } + + self.isRefreshing = true + + let urlString = "\(String(describing: self.baseUrl))/oauth2/token" + + // create params + var parameters = Parameters() + parameters[ParamKey.accessToken] = self.accessToken + parameters[ParamKey.refreshToken] = self.refreshToken + parameters[ParamKey.clientId] = self.clientId + parameters[ParamKey.grantType] = "refresh_token" + + // request + self.sessionManager.request(urlString, method: .post, parameters: parameters, encoding: JSONEncoding.default) + .responseJSON { [weak self] response in + guard let strongSelf = self else { return } + completion(response.result.value as AnyObject) + strongSelf.isRefreshing = false + } + } +} diff --git a/BaseMVC/Sources/Views/Base.lproj/LaunchScreen.storyboard b/BaseMVC/Sources/Views/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..98e239c --- /dev/null +++ b/BaseMVC/Sources/Views/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/BaseMVC/Sources/Views/Base.lproj/Main.storyboard b/BaseMVC/Sources/Views/Base.lproj/Main.storyboard new file mode 100644 index 0000000..3645a2b --- /dev/null +++ b/BaseMVC/Sources/Views/Base.lproj/Main.storyboard @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Podfile b/Podfile new file mode 100644 index 0000000..ead1708 --- /dev/null +++ b/Podfile @@ -0,0 +1,13 @@ +# Uncomment the next line to define a global platform for your project +platform :ios, '9.0' + +target 'BaseMVC' do + # Comment the next line if you're not using Swift and don't want to use dynamic frameworks + use_frameworks! + + # Pods for BaseMVC + pod 'Alamofire', '~> 4.4' + pod 'SwiftyJSON' + pod 'RealmSwift' + +end diff --git a/Podfile.lock b/Podfile.lock new file mode 100644 index 0000000..61f53e4 --- /dev/null +++ b/Podfile.lock @@ -0,0 +1,23 @@ +PODS: + - Alamofire (4.4.0) + - Realm (2.8.1): + - Realm/Headers (= 2.8.1) + - Realm/Headers (2.8.1) + - RealmSwift (2.8.1): + - Realm (= 2.8.1) + - SwiftyJSON (3.1.4) + +DEPENDENCIES: + - Alamofire (~> 4.4) + - RealmSwift + - SwiftyJSON + +SPEC CHECKSUMS: + Alamofire: dc44b1600b800eb63da6a19039a0083d62a6a62d + Realm: 2627602ad6818451f0cb8c2a6e072f7f10a5f360 + RealmSwift: 4764ca7657f2193c256fb032c0b123926f70dbcd + SwiftyJSON: c2842d878f95482ffceec5709abc3d05680c0220 + +PODFILE CHECKSUM: 85c5158b9d1f2572801568da02b6a23bbed20411 + +COCOAPODS: 1.2.1