├── .gitignore ├── LICENSE ├── MoltinSwiftExample ├── MoltinSwiftExample.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata ├── MoltinSwiftExample.xcworkspace │ └── contents.xcworkspacedata ├── MoltinSwiftExample │ ├── AddressEntryTableViewController.swift │ ├── AlertDialog.swift │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── LaunchScreen.xib │ │ └── Main.storyboard │ ├── CartTableViewCell.swift │ ├── CartViewController.swift │ ├── CollectionTableViewCell.swift │ ├── CollectionsViewController.swift │ ├── ContinueButtonTableViewCell.swift │ ├── DataEntryTextField.swift │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-60@2x.png │ │ │ ├── Icon-60@3x.png │ │ │ ├── Icon-Small-40@2x.png │ │ │ ├── Icon-Small-40@3x.png │ │ │ ├── Icon-Small@2x.png │ │ │ └── Icon-Small@3x.png │ │ ├── first.imageset │ │ │ ├── Contents.json │ │ │ └── collection_stack.pdf │ │ └── second.imageset │ │ │ ├── Contents.json │ │ │ └── cart.pdf │ ├── Info.plist │ ├── PaymentViewController.swift │ ├── ProductDetailViewController.swift │ ├── ProductListTableViewController.swift │ ├── ProductsListTableViewCell.swift │ ├── ProductsLoadMoreTableViewCell.swift │ ├── Project-Bridging-Header.h │ ├── ShippingMethodTableViewCell.swift │ ├── ShippingTableViewController.swift │ ├── String+Numeric.swift │ ├── SwiftSpinner.swift │ ├── SwitchTableViewCell.swift │ └── TextEntryTableViewCell.swift ├── Podfile └── Podfile.lock └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/swift,osx 2 | 3 | ### Swift ### 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 | *.xccheckout 25 | *.moved-aside 26 | *.xcuserstate 27 | *.xcscmblueprint 28 | 29 | ## Obj-C/Swift specific 30 | *.hmap 31 | *.ipa 32 | 33 | # CocoaPods 34 | # 35 | # We recommend against adding the Pods directory to your .gitignore. However 36 | # you should judge for yourself, the pros and cons are mentioned at: 37 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 38 | # 39 | Pods/ 40 | 41 | # Carthage 42 | # 43 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 44 | # Carthage/Checkouts 45 | 46 | Carthage/Build 47 | 48 | 49 | ### OSX ### 50 | .DS_Store 51 | .AppleDouble 52 | .LSOverride 53 | 54 | # Icon must end with two \r 55 | Icon 56 | 57 | 58 | # Thumbnails 59 | ._* 60 | 61 | # Files that might appear in the root of a volume 62 | .DocumentRevisions-V100 63 | .fseventsd 64 | .Spotlight-V100 65 | .TemporaryItems 66 | .Trashes 67 | .VolumeIcon.icns 68 | 69 | # Directories potentially created on remote AFP share 70 | .AppleDB 71 | .AppleDesktop 72 | Network Trash Folder 73 | Temporary Items 74 | .apdisk 75 | 76 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Moltin Ltd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 9651F25CDDDB591CF7E76AFB /* Pods_MoltinSwiftExample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA3ED1809EC21A062C2BC85D /* Pods_MoltinSwiftExample.framework */; }; 11 | BA0403621B820FF200ED8063 /* ShippingTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA0403611B820FF200ED8063 /* ShippingTableViewController.swift */; }; 12 | BA0403641B8211FB00ED8063 /* ShippingMethodTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA0403631B8211FB00ED8063 /* ShippingMethodTableViewCell.swift */; }; 13 | BA0403661B822C7A00ED8063 /* TextEntryTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA0403651B822C7A00ED8063 /* TextEntryTableViewCell.swift */; }; 14 | BA04036A1B82629C00ED8063 /* AddressEntryTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA0403691B82629C00ED8063 /* AddressEntryTableViewController.swift */; }; 15 | BA55A9CD1B85F24800F928F4 /* PaymentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA55A9CC1B85F24800F928F4 /* PaymentViewController.swift */; }; 16 | BA5728471B7FB0A300F5D919 /* CollectionTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA5728461B7FB0A300F5D919 /* CollectionTableViewCell.swift */; }; 17 | BA62A4B21B8339E6009D9BD2 /* AlertDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA62A4B11B8339E6009D9BD2 /* AlertDialog.swift */; }; 18 | BA70854E1B8780CF005049E0 /* DataEntryTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA70854D1B8780CF005049E0 /* DataEntryTextField.swift */; }; 19 | BA7085501B87859E005049E0 /* String+Numeric.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA70854F1B87859E005049E0 /* String+Numeric.swift */; }; 20 | BA746BAF1DAAA15500D56E17 /* SwiftSpinner.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA746BAE1DAAA15500D56E17 /* SwiftSpinner.swift */; }; 21 | BA9637981B80B13600BCD1E0 /* ProductListTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA9637971B80B13600BCD1E0 /* ProductListTableViewController.swift */; }; 22 | BA96379C1B80B29B00BCD1E0 /* ProductsListTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA96379B1B80B29B00BCD1E0 /* ProductsListTableViewCell.swift */; }; 23 | BA96379E1B80B72D00BCD1E0 /* ProductsLoadMoreTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA96379D1B80B72D00BCD1E0 /* ProductsLoadMoreTableViewCell.swift */; }; 24 | BA9637A01B80F9E500BCD1E0 /* ProductDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA96379F1B80F9E500BCD1E0 /* ProductDetailViewController.swift */; }; 25 | BA9637A21B81108900BCD1E0 /* CartTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA9637A11B81108900BCD1E0 /* CartTableViewCell.swift */; }; 26 | BAB1D4021B7F6D9E000F07A1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB1D4011B7F6D9E000F07A1 /* AppDelegate.swift */; }; 27 | BAB1D4041B7F6D9E000F07A1 /* CollectionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB1D4031B7F6D9E000F07A1 /* CollectionsViewController.swift */; }; 28 | BAB1D4061B7F6D9E000F07A1 /* CartViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB1D4051B7F6D9E000F07A1 /* CartViewController.swift */; }; 29 | BAB1D4091B7F6D9E000F07A1 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BAB1D4071B7F6D9E000F07A1 /* Main.storyboard */; }; 30 | BAB1D40B1B7F6D9E000F07A1 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BAB1D40A1B7F6D9E000F07A1 /* Images.xcassets */; }; 31 | BAB1D40E1B7F6D9E000F07A1 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = BAB1D40C1B7F6D9E000F07A1 /* LaunchScreen.xib */; }; 32 | BAE268121B83CBC9006E6DE6 /* ContinueButtonTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAE268111B83CBC9006E6DE6 /* ContinueButtonTableViewCell.swift */; }; 33 | BAE268141B83DDC7006E6DE6 /* SwitchTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAE268131B83DDC7006E6DE6 /* SwitchTableViewCell.swift */; }; 34 | /* End PBXBuildFile section */ 35 | 36 | /* Begin PBXFileReference section */ 37 | 52363A3DAD62B5E9E3126802 /* Pods-MoltinSwiftExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MoltinSwiftExample.release.xcconfig"; path = "Pods/Target Support Files/Pods-MoltinSwiftExample/Pods-MoltinSwiftExample.release.xcconfig"; sourceTree = ""; }; 38 | 8C788A53F21ECC575E53E53F /* Pods-MoltinSwiftExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MoltinSwiftExample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-MoltinSwiftExample/Pods-MoltinSwiftExample.debug.xcconfig"; sourceTree = ""; }; 39 | 9C016F393761A3A38D2F0FE9 /* Pods.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 40 | BA0403611B820FF200ED8063 /* ShippingTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShippingTableViewController.swift; sourceTree = ""; }; 41 | BA0403631B8211FB00ED8063 /* ShippingMethodTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShippingMethodTableViewCell.swift; sourceTree = ""; }; 42 | BA0403651B822C7A00ED8063 /* TextEntryTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextEntryTableViewCell.swift; sourceTree = ""; }; 43 | BA0403691B82629C00ED8063 /* AddressEntryTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddressEntryTableViewController.swift; sourceTree = ""; }; 44 | BA55A9CC1B85F24800F928F4 /* PaymentViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentViewController.swift; sourceTree = ""; }; 45 | BA5728461B7FB0A300F5D919 /* CollectionTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionTableViewCell.swift; sourceTree = ""; }; 46 | BA62A4B11B8339E6009D9BD2 /* AlertDialog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertDialog.swift; sourceTree = ""; }; 47 | BA70854D1B8780CF005049E0 /* DataEntryTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataEntryTextField.swift; sourceTree = ""; }; 48 | BA70854F1B87859E005049E0 /* String+Numeric.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Numeric.swift"; sourceTree = ""; }; 49 | BA746BAE1DAAA15500D56E17 /* SwiftSpinner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftSpinner.swift; sourceTree = ""; }; 50 | BA9637971B80B13600BCD1E0 /* ProductListTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProductListTableViewController.swift; sourceTree = ""; }; 51 | BA96379B1B80B29B00BCD1E0 /* ProductsListTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProductsListTableViewCell.swift; sourceTree = ""; }; 52 | BA96379D1B80B72D00BCD1E0 /* ProductsLoadMoreTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProductsLoadMoreTableViewCell.swift; sourceTree = ""; }; 53 | BA96379F1B80F9E500BCD1E0 /* ProductDetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProductDetailViewController.swift; sourceTree = ""; }; 54 | BA9637A11B81108900BCD1E0 /* CartTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CartTableViewCell.swift; sourceTree = ""; }; 55 | BAB1D3FC1B7F6D9E000F07A1 /* MoltinSwiftExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MoltinSwiftExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 56 | BAB1D4001B7F6D9E000F07A1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 57 | BAB1D4011B7F6D9E000F07A1 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 58 | BAB1D4031B7F6D9E000F07A1 /* CollectionsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionsViewController.swift; sourceTree = ""; }; 59 | BAB1D4051B7F6D9E000F07A1 /* CartViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CartViewController.swift; sourceTree = ""; }; 60 | BAB1D4081B7F6D9E000F07A1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 61 | BAB1D40A1B7F6D9E000F07A1 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 62 | BAB1D40D1B7F6D9E000F07A1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 63 | BAB1D4231B7F6F5E000F07A1 /* Project-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Project-Bridging-Header.h"; sourceTree = ""; }; 64 | BAE268111B83CBC9006E6DE6 /* ContinueButtonTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContinueButtonTableViewCell.swift; sourceTree = ""; }; 65 | BAE268131B83DDC7006E6DE6 /* SwitchTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwitchTableViewCell.swift; sourceTree = ""; }; 66 | DA3ED1809EC21A062C2BC85D /* Pods_MoltinSwiftExample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MoltinSwiftExample.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 67 | /* End PBXFileReference section */ 68 | 69 | /* Begin PBXFrameworksBuildPhase section */ 70 | BAB1D3F91B7F6D9E000F07A1 /* Frameworks */ = { 71 | isa = PBXFrameworksBuildPhase; 72 | buildActionMask = 2147483647; 73 | files = ( 74 | 9651F25CDDDB591CF7E76AFB /* Pods_MoltinSwiftExample.framework in Frameworks */, 75 | ); 76 | runOnlyForDeploymentPostprocessing = 0; 77 | }; 78 | /* End PBXFrameworksBuildPhase section */ 79 | 80 | /* Begin PBXGroup section */ 81 | 46076447C5D3C0E72A6A554E /* Pods */ = { 82 | isa = PBXGroup; 83 | children = ( 84 | 8C788A53F21ECC575E53E53F /* Pods-MoltinSwiftExample.debug.xcconfig */, 85 | 52363A3DAD62B5E9E3126802 /* Pods-MoltinSwiftExample.release.xcconfig */, 86 | ); 87 | name = Pods; 88 | sourceTree = ""; 89 | }; 90 | BA9637991B80B14800BCD1E0 /* View controllers */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | BAB1D4031B7F6D9E000F07A1 /* CollectionsViewController.swift */, 94 | BAB1D4051B7F6D9E000F07A1 /* CartViewController.swift */, 95 | BA9637971B80B13600BCD1E0 /* ProductListTableViewController.swift */, 96 | BA96379F1B80F9E500BCD1E0 /* ProductDetailViewController.swift */, 97 | BA0403611B820FF200ED8063 /* ShippingTableViewController.swift */, 98 | BA0403691B82629C00ED8063 /* AddressEntryTableViewController.swift */, 99 | BA55A9CC1B85F24800F928F4 /* PaymentViewController.swift */, 100 | ); 101 | name = "View controllers"; 102 | sourceTree = ""; 103 | }; 104 | BA96379A1B80B15200BCD1E0 /* Table cells */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | BA5728461B7FB0A300F5D919 /* CollectionTableViewCell.swift */, 108 | BA96379B1B80B29B00BCD1E0 /* ProductsListTableViewCell.swift */, 109 | BA96379D1B80B72D00BCD1E0 /* ProductsLoadMoreTableViewCell.swift */, 110 | BA9637A11B81108900BCD1E0 /* CartTableViewCell.swift */, 111 | BA0403631B8211FB00ED8063 /* ShippingMethodTableViewCell.swift */, 112 | BA0403651B822C7A00ED8063 /* TextEntryTableViewCell.swift */, 113 | BAE268111B83CBC9006E6DE6 /* ContinueButtonTableViewCell.swift */, 114 | BAE268131B83DDC7006E6DE6 /* SwitchTableViewCell.swift */, 115 | BA70854D1B8780CF005049E0 /* DataEntryTextField.swift */, 116 | ); 117 | name = "Table cells"; 118 | sourceTree = ""; 119 | }; 120 | BAB1D3F31B7F6D9E000F07A1 = { 121 | isa = PBXGroup; 122 | children = ( 123 | BAB1D3FE1B7F6D9E000F07A1 /* MoltinSwiftExample */, 124 | BAB1D3FD1B7F6D9E000F07A1 /* Products */, 125 | F2483C18BCFF6CAB5D2153D1 /* Frameworks */, 126 | 46076447C5D3C0E72A6A554E /* Pods */, 127 | ); 128 | sourceTree = ""; 129 | }; 130 | BAB1D3FD1B7F6D9E000F07A1 /* Products */ = { 131 | isa = PBXGroup; 132 | children = ( 133 | BAB1D3FC1B7F6D9E000F07A1 /* MoltinSwiftExample.app */, 134 | ); 135 | name = Products; 136 | sourceTree = ""; 137 | }; 138 | BAB1D3FE1B7F6D9E000F07A1 /* MoltinSwiftExample */ = { 139 | isa = PBXGroup; 140 | children = ( 141 | BA96379A1B80B15200BCD1E0 /* Table cells */, 142 | BA9637991B80B14800BCD1E0 /* View controllers */, 143 | BAB1D4011B7F6D9E000F07A1 /* AppDelegate.swift */, 144 | BA62A4B11B8339E6009D9BD2 /* AlertDialog.swift */, 145 | BA70854F1B87859E005049E0 /* String+Numeric.swift */, 146 | BAB1D4071B7F6D9E000F07A1 /* Main.storyboard */, 147 | BAB1D40A1B7F6D9E000F07A1 /* Images.xcassets */, 148 | BAB1D40C1B7F6D9E000F07A1 /* LaunchScreen.xib */, 149 | BAB1D3FF1B7F6D9E000F07A1 /* Supporting Files */, 150 | BAB1D4231B7F6F5E000F07A1 /* Project-Bridging-Header.h */, 151 | BA746BAE1DAAA15500D56E17 /* SwiftSpinner.swift */, 152 | ); 153 | path = MoltinSwiftExample; 154 | sourceTree = ""; 155 | }; 156 | BAB1D3FF1B7F6D9E000F07A1 /* Supporting Files */ = { 157 | isa = PBXGroup; 158 | children = ( 159 | BAB1D4001B7F6D9E000F07A1 /* Info.plist */, 160 | ); 161 | name = "Supporting Files"; 162 | sourceTree = ""; 163 | }; 164 | F2483C18BCFF6CAB5D2153D1 /* Frameworks */ = { 165 | isa = PBXGroup; 166 | children = ( 167 | 9C016F393761A3A38D2F0FE9 /* Pods.framework */, 168 | DA3ED1809EC21A062C2BC85D /* Pods_MoltinSwiftExample.framework */, 169 | ); 170 | name = Frameworks; 171 | sourceTree = ""; 172 | }; 173 | /* End PBXGroup section */ 174 | 175 | /* Begin PBXNativeTarget section */ 176 | BAB1D3FB1B7F6D9E000F07A1 /* MoltinSwiftExample */ = { 177 | isa = PBXNativeTarget; 178 | buildConfigurationList = BAB1D41D1B7F6D9E000F07A1 /* Build configuration list for PBXNativeTarget "MoltinSwiftExample" */; 179 | buildPhases = ( 180 | BCFA5F35014F7907A85EF47A /* 📦 Check Pods Manifest.lock */, 181 | BAB1D3F81B7F6D9E000F07A1 /* Sources */, 182 | BAB1D3F91B7F6D9E000F07A1 /* Frameworks */, 183 | BAB1D3FA1B7F6D9E000F07A1 /* Resources */, 184 | A96A828B723440D89CAE9540 /* 📦 Embed Pods Frameworks */, 185 | C7984EEE83C8C19EBE66A205 /* 📦 Copy Pods Resources */, 186 | ); 187 | buildRules = ( 188 | ); 189 | dependencies = ( 190 | ); 191 | name = MoltinSwiftExample; 192 | productName = MoltinSwiftExample; 193 | productReference = BAB1D3FC1B7F6D9E000F07A1 /* MoltinSwiftExample.app */; 194 | productType = "com.apple.product-type.application"; 195 | }; 196 | /* End PBXNativeTarget section */ 197 | 198 | /* Begin PBXProject section */ 199 | BAB1D3F41B7F6D9E000F07A1 /* Project object */ = { 200 | isa = PBXProject; 201 | attributes = { 202 | LastSwiftMigration = 0700; 203 | LastSwiftUpdateCheck = 0700; 204 | LastUpgradeCheck = 0800; 205 | ORGANIZATIONNAME = Moltin; 206 | TargetAttributes = { 207 | BAB1D3FB1B7F6D9E000F07A1 = { 208 | CreatedOnToolsVersion = 6.4; 209 | LastSwiftMigration = 0800; 210 | }; 211 | }; 212 | }; 213 | buildConfigurationList = BAB1D3F71B7F6D9E000F07A1 /* Build configuration list for PBXProject "MoltinSwiftExample" */; 214 | compatibilityVersion = "Xcode 3.2"; 215 | developmentRegion = English; 216 | hasScannedForEncodings = 0; 217 | knownRegions = ( 218 | en, 219 | Base, 220 | ); 221 | mainGroup = BAB1D3F31B7F6D9E000F07A1; 222 | productRefGroup = BAB1D3FD1B7F6D9E000F07A1 /* Products */; 223 | projectDirPath = ""; 224 | projectRoot = ""; 225 | targets = ( 226 | BAB1D3FB1B7F6D9E000F07A1 /* MoltinSwiftExample */, 227 | ); 228 | }; 229 | /* End PBXProject section */ 230 | 231 | /* Begin PBXResourcesBuildPhase section */ 232 | BAB1D3FA1B7F6D9E000F07A1 /* Resources */ = { 233 | isa = PBXResourcesBuildPhase; 234 | buildActionMask = 2147483647; 235 | files = ( 236 | BAB1D4091B7F6D9E000F07A1 /* Main.storyboard in Resources */, 237 | BAB1D40E1B7F6D9E000F07A1 /* LaunchScreen.xib in Resources */, 238 | BAB1D40B1B7F6D9E000F07A1 /* Images.xcassets in Resources */, 239 | ); 240 | runOnlyForDeploymentPostprocessing = 0; 241 | }; 242 | /* End PBXResourcesBuildPhase section */ 243 | 244 | /* Begin PBXShellScriptBuildPhase section */ 245 | A96A828B723440D89CAE9540 /* 📦 Embed Pods Frameworks */ = { 246 | isa = PBXShellScriptBuildPhase; 247 | buildActionMask = 2147483647; 248 | files = ( 249 | ); 250 | inputPaths = ( 251 | ); 252 | name = "📦 Embed Pods Frameworks"; 253 | outputPaths = ( 254 | ); 255 | runOnlyForDeploymentPostprocessing = 0; 256 | shellPath = /bin/sh; 257 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-MoltinSwiftExample/Pods-MoltinSwiftExample-frameworks.sh\"\n"; 258 | showEnvVarsInLog = 0; 259 | }; 260 | BCFA5F35014F7907A85EF47A /* 📦 Check Pods Manifest.lock */ = { 261 | isa = PBXShellScriptBuildPhase; 262 | buildActionMask = 2147483647; 263 | files = ( 264 | ); 265 | inputPaths = ( 266 | ); 267 | name = "📦 Check Pods Manifest.lock"; 268 | outputPaths = ( 269 | ); 270 | runOnlyForDeploymentPostprocessing = 0; 271 | shellPath = /bin/sh; 272 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; 273 | showEnvVarsInLog = 0; 274 | }; 275 | C7984EEE83C8C19EBE66A205 /* 📦 Copy Pods Resources */ = { 276 | isa = PBXShellScriptBuildPhase; 277 | buildActionMask = 2147483647; 278 | files = ( 279 | ); 280 | inputPaths = ( 281 | ); 282 | name = "📦 Copy Pods Resources"; 283 | outputPaths = ( 284 | ); 285 | runOnlyForDeploymentPostprocessing = 0; 286 | shellPath = /bin/sh; 287 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-MoltinSwiftExample/Pods-MoltinSwiftExample-resources.sh\"\n"; 288 | showEnvVarsInLog = 0; 289 | }; 290 | /* End PBXShellScriptBuildPhase section */ 291 | 292 | /* Begin PBXSourcesBuildPhase section */ 293 | BAB1D3F81B7F6D9E000F07A1 /* Sources */ = { 294 | isa = PBXSourcesBuildPhase; 295 | buildActionMask = 2147483647; 296 | files = ( 297 | BAB1D4061B7F6D9E000F07A1 /* CartViewController.swift in Sources */, 298 | BAB1D4021B7F6D9E000F07A1 /* AppDelegate.swift in Sources */, 299 | BAB1D4041B7F6D9E000F07A1 /* CollectionsViewController.swift in Sources */, 300 | BA9637A21B81108900BCD1E0 /* CartTableViewCell.swift in Sources */, 301 | BA62A4B21B8339E6009D9BD2 /* AlertDialog.swift in Sources */, 302 | BA746BAF1DAAA15500D56E17 /* SwiftSpinner.swift in Sources */, 303 | BA04036A1B82629C00ED8063 /* AddressEntryTableViewController.swift in Sources */, 304 | BA70854E1B8780CF005049E0 /* DataEntryTextField.swift in Sources */, 305 | BA96379E1B80B72D00BCD1E0 /* ProductsLoadMoreTableViewCell.swift in Sources */, 306 | BA55A9CD1B85F24800F928F4 /* PaymentViewController.swift in Sources */, 307 | BA0403661B822C7A00ED8063 /* TextEntryTableViewCell.swift in Sources */, 308 | BA9637981B80B13600BCD1E0 /* ProductListTableViewController.swift in Sources */, 309 | BAE268141B83DDC7006E6DE6 /* SwitchTableViewCell.swift in Sources */, 310 | BA9637A01B80F9E500BCD1E0 /* ProductDetailViewController.swift in Sources */, 311 | BA0403641B8211FB00ED8063 /* ShippingMethodTableViewCell.swift in Sources */, 312 | BAE268121B83CBC9006E6DE6 /* ContinueButtonTableViewCell.swift in Sources */, 313 | BA5728471B7FB0A300F5D919 /* CollectionTableViewCell.swift in Sources */, 314 | BA96379C1B80B29B00BCD1E0 /* ProductsListTableViewCell.swift in Sources */, 315 | BA7085501B87859E005049E0 /* String+Numeric.swift in Sources */, 316 | BA0403621B820FF200ED8063 /* ShippingTableViewController.swift in Sources */, 317 | ); 318 | runOnlyForDeploymentPostprocessing = 0; 319 | }; 320 | /* End PBXSourcesBuildPhase section */ 321 | 322 | /* Begin PBXVariantGroup section */ 323 | BAB1D4071B7F6D9E000F07A1 /* Main.storyboard */ = { 324 | isa = PBXVariantGroup; 325 | children = ( 326 | BAB1D4081B7F6D9E000F07A1 /* Base */, 327 | ); 328 | name = Main.storyboard; 329 | sourceTree = ""; 330 | }; 331 | BAB1D40C1B7F6D9E000F07A1 /* LaunchScreen.xib */ = { 332 | isa = PBXVariantGroup; 333 | children = ( 334 | BAB1D40D1B7F6D9E000F07A1 /* Base */, 335 | ); 336 | name = LaunchScreen.xib; 337 | sourceTree = ""; 338 | }; 339 | /* End PBXVariantGroup section */ 340 | 341 | /* Begin XCBuildConfiguration section */ 342 | BAB1D41B1B7F6D9E000F07A1 /* Debug */ = { 343 | isa = XCBuildConfiguration; 344 | buildSettings = { 345 | ALWAYS_SEARCH_USER_PATHS = NO; 346 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 347 | CLANG_CXX_LIBRARY = "libc++"; 348 | CLANG_ENABLE_MODULES = YES; 349 | CLANG_ENABLE_OBJC_ARC = YES; 350 | CLANG_WARN_BOOL_CONVERSION = YES; 351 | CLANG_WARN_CONSTANT_CONVERSION = YES; 352 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 353 | CLANG_WARN_EMPTY_BODY = YES; 354 | CLANG_WARN_ENUM_CONVERSION = YES; 355 | CLANG_WARN_INFINITE_RECURSION = YES; 356 | CLANG_WARN_INT_CONVERSION = YES; 357 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 358 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 359 | CLANG_WARN_UNREACHABLE_CODE = YES; 360 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 361 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 362 | COPY_PHASE_STRIP = NO; 363 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 364 | ENABLE_STRICT_OBJC_MSGSEND = YES; 365 | ENABLE_TESTABILITY = YES; 366 | GCC_C_LANGUAGE_STANDARD = gnu99; 367 | GCC_DYNAMIC_NO_PIC = NO; 368 | GCC_NO_COMMON_BLOCKS = YES; 369 | GCC_OPTIMIZATION_LEVEL = 0; 370 | GCC_PREPROCESSOR_DEFINITIONS = ( 371 | "DEBUG=1", 372 | "$(inherited)", 373 | ); 374 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 375 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 376 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 377 | GCC_WARN_UNDECLARED_SELECTOR = YES; 378 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 379 | GCC_WARN_UNUSED_FUNCTION = YES; 380 | GCC_WARN_UNUSED_VARIABLE = YES; 381 | IPHONEOS_DEPLOYMENT_TARGET = 8.4; 382 | MTL_ENABLE_DEBUG_INFO = YES; 383 | ONLY_ACTIVE_ARCH = YES; 384 | SDKROOT = iphoneos; 385 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 386 | }; 387 | name = Debug; 388 | }; 389 | BAB1D41C1B7F6D9E000F07A1 /* Release */ = { 390 | isa = XCBuildConfiguration; 391 | buildSettings = { 392 | ALWAYS_SEARCH_USER_PATHS = NO; 393 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 394 | CLANG_CXX_LIBRARY = "libc++"; 395 | CLANG_ENABLE_MODULES = YES; 396 | CLANG_ENABLE_OBJC_ARC = YES; 397 | CLANG_WARN_BOOL_CONVERSION = YES; 398 | CLANG_WARN_CONSTANT_CONVERSION = YES; 399 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 400 | CLANG_WARN_EMPTY_BODY = YES; 401 | CLANG_WARN_ENUM_CONVERSION = YES; 402 | CLANG_WARN_INFINITE_RECURSION = YES; 403 | CLANG_WARN_INT_CONVERSION = YES; 404 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 405 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 406 | CLANG_WARN_UNREACHABLE_CODE = YES; 407 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 408 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 409 | COPY_PHASE_STRIP = NO; 410 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 411 | ENABLE_NS_ASSERTIONS = NO; 412 | ENABLE_STRICT_OBJC_MSGSEND = YES; 413 | GCC_C_LANGUAGE_STANDARD = gnu99; 414 | GCC_NO_COMMON_BLOCKS = YES; 415 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 416 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 417 | GCC_WARN_UNDECLARED_SELECTOR = YES; 418 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 419 | GCC_WARN_UNUSED_FUNCTION = YES; 420 | GCC_WARN_UNUSED_VARIABLE = YES; 421 | IPHONEOS_DEPLOYMENT_TARGET = 8.4; 422 | MTL_ENABLE_DEBUG_INFO = NO; 423 | SDKROOT = iphoneos; 424 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 425 | VALIDATE_PRODUCT = YES; 426 | }; 427 | name = Release; 428 | }; 429 | BAB1D41E1B7F6D9E000F07A1 /* Debug */ = { 430 | isa = XCBuildConfiguration; 431 | baseConfigurationReference = 8C788A53F21ECC575E53E53F /* Pods-MoltinSwiftExample.debug.xcconfig */; 432 | buildSettings = { 433 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 434 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 435 | INFOPLIST_FILE = MoltinSwiftExample/Info.plist; 436 | IPHONEOS_DEPLOYMENT_TARGET = 8.4; 437 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 438 | PRODUCT_BUNDLE_IDENTIFIER = "com.moltin.$(PRODUCT_NAME:rfc1034identifier)"; 439 | PRODUCT_NAME = "$(TARGET_NAME)"; 440 | SWIFT_OBJC_BRIDGING_HEADER = "MoltinSwiftExample/Project-Bridging-Header.h"; 441 | SWIFT_VERSION = 3.0; 442 | }; 443 | name = Debug; 444 | }; 445 | BAB1D41F1B7F6D9E000F07A1 /* Release */ = { 446 | isa = XCBuildConfiguration; 447 | baseConfigurationReference = 52363A3DAD62B5E9E3126802 /* Pods-MoltinSwiftExample.release.xcconfig */; 448 | buildSettings = { 449 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 450 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 451 | INFOPLIST_FILE = MoltinSwiftExample/Info.plist; 452 | IPHONEOS_DEPLOYMENT_TARGET = 8.4; 453 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 454 | PRODUCT_BUNDLE_IDENTIFIER = "com.moltin.$(PRODUCT_NAME:rfc1034identifier)"; 455 | PRODUCT_NAME = "$(TARGET_NAME)"; 456 | SWIFT_OBJC_BRIDGING_HEADER = "MoltinSwiftExample/Project-Bridging-Header.h"; 457 | SWIFT_VERSION = 3.0; 458 | }; 459 | name = Release; 460 | }; 461 | /* End XCBuildConfiguration section */ 462 | 463 | /* Begin XCConfigurationList section */ 464 | BAB1D3F71B7F6D9E000F07A1 /* Build configuration list for PBXProject "MoltinSwiftExample" */ = { 465 | isa = XCConfigurationList; 466 | buildConfigurations = ( 467 | BAB1D41B1B7F6D9E000F07A1 /* Debug */, 468 | BAB1D41C1B7F6D9E000F07A1 /* Release */, 469 | ); 470 | defaultConfigurationIsVisible = 0; 471 | defaultConfigurationName = Release; 472 | }; 473 | BAB1D41D1B7F6D9E000F07A1 /* Build configuration list for PBXNativeTarget "MoltinSwiftExample" */ = { 474 | isa = XCConfigurationList; 475 | buildConfigurations = ( 476 | BAB1D41E1B7F6D9E000F07A1 /* Debug */, 477 | BAB1D41F1B7F6D9E000F07A1 /* Release */, 478 | ); 479 | defaultConfigurationIsVisible = 0; 480 | defaultConfigurationName = Release; 481 | }; 482 | /* End XCConfigurationList section */ 483 | }; 484 | rootObject = BAB1D3F41B7F6D9E000F07A1 /* Project object */; 485 | } 486 | -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample/AddressEntryTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AddressEntryTableViewController.swift 3 | // MoltinSwiftExample 4 | // 5 | // Created by Dylan McKee on 17/08/2015. 6 | // Copyright (c) 2015 Moltin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Moltin 11 | 12 | class AddressEntryTableViewController: UITableViewController, UIPickerViewDelegate, UIPickerViewDataSource, SwitchTableViewCellDelegate, TextEntryTableViewCellDelegate { 13 | 14 | var emailAddress:String? 15 | var billingDictionary:Dictionary? 16 | var shippingDictionary:Dictionary? 17 | 18 | var contactFieldsArray = Array>() 19 | 20 | // Assume it's the billing contact address by default, unless told that it's the shipping address. 21 | var isShippingAddress = false 22 | 23 | var countryArray:Array>? 24 | 25 | fileprivate var useSameShippingAddress = false 26 | 27 | fileprivate var selectedCountryIndex:Int? 28 | 29 | // Field identifier key constants 30 | fileprivate let contactEmailFieldIdentifier = "email" 31 | fileprivate let contactFirstNameFieldIdentifier = "first_name" 32 | fileprivate let contactLastNameFieldIdentifier = "last_name" 33 | fileprivate let address1FieldIdentifier = "address_1" 34 | fileprivate let address2FieldIdentifier = "address_2" 35 | fileprivate let cityFieldIdentifier = "city" 36 | fileprivate let stateFieldIdentifier = "state" 37 | fileprivate let countryFieldIdentifier = "country" 38 | fileprivate let postcodeFieldIdentifier = "postcode" 39 | 40 | fileprivate let countryPickerView = UIPickerView() 41 | 42 | fileprivate let BILLING_ADDRESS_SHIPPING_SEGUE = "billingShippingSegue" 43 | fileprivate let SHIPPING_ADDRESS_SHIPPING_SEGUE = "shippingShippingSegue" 44 | fileprivate let SHIPPING_ADDRESS_SEGUE = "shippingAddressSegue" 45 | 46 | 47 | //MARK: - View loading 48 | override func viewDidLoad() { 49 | super.viewDidLoad() 50 | 51 | var fields = [contactFirstNameFieldIdentifier, contactLastNameFieldIdentifier, address1FieldIdentifier, address2FieldIdentifier, cityFieldIdentifier, stateFieldIdentifier, countryFieldIdentifier, postcodeFieldIdentifier] 52 | 53 | if !isShippingAddress { 54 | // Set-up extra billing address fields... 55 | fields = [contactEmailFieldIdentifier, contactFirstNameFieldIdentifier, contactLastNameFieldIdentifier, address1FieldIdentifier, address2FieldIdentifier, cityFieldIdentifier, stateFieldIdentifier, countryFieldIdentifier, postcodeFieldIdentifier] 56 | 57 | billingDictionary = Dictionary() 58 | 59 | self.title = "Billing Address" 60 | 61 | } else { 62 | shippingDictionary = Dictionary() 63 | 64 | self.title = "Shipping Address" 65 | 66 | } 67 | 68 | for field in fields { 69 | var userPresentableName = field.replacingOccurrences(of: "_", with: " ") 70 | userPresentableName = userPresentableName.capitalized 71 | 72 | var fieldDict = Dictionary() 73 | fieldDict["name"] = userPresentableName 74 | fieldDict["identifier"] = field 75 | 76 | contactFieldsArray.append(fieldDict) 77 | 78 | } 79 | 80 | // If country array is blank, let's fetch it... 81 | if (countryArray == nil) { 82 | SwiftSpinner.show("Loading countries") 83 | 84 | // Fetch countries from Moltin API, showing loading animation whilst this async fetch is happening. 85 | Moltin.sharedInstance().address.fields(withCustomerId: "", andAddressId: "", success: { (response) -> Void in 86 | // Got a response, let's extract the countries... 87 | let responseDict = NSDictionary(dictionary: response!) 88 | let tmpCountries = responseDict.value(forKeyPath: "result.country.available") as! NSDictionary 89 | 90 | self.countryArray = Array>() 91 | 92 | // Country codes are stored as keys, their values contain dicts, which in turn contain the country names. 93 | for countryCode in tmpCountries.allKeys { 94 | var newCountry = Dictionary() 95 | if let codeString = countryCode as? String { 96 | newCountry["code"] = codeString 97 | newCountry["name"] = (tmpCountries.value(forKey: codeString) as! String) 98 | } 99 | self.countryArray?.append(newCountry) 100 | } 101 | 102 | 103 | // and hide loading UI. 104 | SwiftSpinner.hide() 105 | 106 | 107 | }, failure: { (response, error) -> Void in 108 | // Something went wrong, alert user. 109 | SwiftSpinner.hide() 110 | 111 | AlertDialog.showAlert("Error", message: "Sorry, could not load countries", viewController: self) 112 | print("Something went wrong...") 113 | print(error) 114 | }) 115 | } 116 | 117 | countryPickerView.delegate = self 118 | countryPickerView.dataSource = self 119 | countryPickerView.backgroundColor = UIColor.white 120 | countryPickerView.isOpaque = true 121 | 122 | // Load table data 123 | self.tableView.reloadData() 124 | 125 | } 126 | 127 | // MARK: - Table view data source 128 | 129 | override func numberOfSections(in tableView: UITableView) -> Int { 130 | // Return the number of sections. 131 | return 1 132 | } 133 | 134 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 135 | // Return the number of rows in the section. 136 | 137 | if !isShippingAddress { 138 | return (contactFieldsArray.count + 2) 139 | } 140 | 141 | return (contactFieldsArray.count + 1) 142 | } 143 | 144 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 145 | 146 | if (((indexPath as NSIndexPath).row == contactFieldsArray.count && isShippingAddress) || ((indexPath as NSIndexPath).row == contactFieldsArray.count + 1 && !isShippingAddress)) { 147 | // Show Continue button cell! 148 | let cell = tableView.dequeueReusableCell(withIdentifier: CONTINUE_BUTTON_CELL_IDENTIFIER, for: indexPath) as! ContinueButtonTableViewCell 149 | return cell 150 | 151 | } 152 | 153 | if ((indexPath as NSIndexPath).row == contactFieldsArray.count && !isShippingAddress) { 154 | // Show Switch cell 155 | let cell = tableView.dequeueReusableCell(withIdentifier: SWITCH_TABLE_CELL_REUSE_IDENTIFIER, for: indexPath) as! SwitchTableViewCell 156 | cell.switchLabel?.text = "Shipping address same as billing?" 157 | cell.switchLabel?.tintColor = MOLTIN_COLOR 158 | cell.delegate = self 159 | return cell 160 | 161 | } 162 | 163 | let cell = tableView.dequeueReusableCell(withIdentifier: TEXT_ENTRY_CELL_REUSE_IDENTIFIER, for: indexPath) as! TextEntryTableViewCell 164 | 165 | // Configure the cell... 166 | cell.textField?.placeholder = contactFieldsArray[(indexPath as NSIndexPath).row]["name"]! 167 | let identifier = contactFieldsArray[(indexPath as NSIndexPath).row]["identifier"]! 168 | cell.cellId = identifier 169 | cell.delegate = self 170 | 171 | cell.selectionStyle = UITableViewCellSelectionStyle.none 172 | 173 | 174 | if identifier == countryFieldIdentifier { 175 | // Make the country field non-editable, and attach the country picker view to it. 176 | cell.textField?.inputView = countryPickerView 177 | cell.textField?.setDoneInputAccessoryView() 178 | cell.hideCursor() 179 | } 180 | 181 | var dict = billingDictionary 182 | if isShippingAddress { 183 | dict = shippingDictionary 184 | } 185 | 186 | if let existingEntry = dict![identifier] { 187 | if existingEntry.characters.count > 0 { 188 | cell.textField?.text = existingEntry 189 | } 190 | } 191 | 192 | 193 | 194 | return cell 195 | } 196 | 197 | 198 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 199 | tableView.deselectRow(at: indexPath, animated: true) 200 | 201 | // If the user tapped on the Continue button, continue! 202 | if (((indexPath as NSIndexPath).row == contactFieldsArray.count && isShippingAddress) || ((indexPath as NSIndexPath).row == contactFieldsArray.count + 1 && !isShippingAddress)) { 203 | continueButtonTapped() 204 | return 205 | } 206 | 207 | } 208 | 209 | //MARK: - Country picker delegate and data source 210 | func numberOfComponents(in pickerView: UIPickerView) -> Int { 211 | return 1 212 | } 213 | 214 | func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { 215 | 216 | 217 | return countryArray!.count 218 | } 219 | 220 | func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { 221 | 222 | return countryArray![row]["name"] 223 | } 224 | 225 | func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { 226 | // User's set a country. 227 | selectedCountryIndex = row 228 | 229 | if countryArray == nil { 230 | return 231 | } 232 | 233 | let selectedCountry = countryArray![row]["name"]! 234 | 235 | if isShippingAddress { 236 | shippingDictionary![countryFieldIdentifier] = selectedCountry 237 | } else { 238 | billingDictionary![countryFieldIdentifier] = selectedCountry 239 | } 240 | 241 | self.tableView.reloadData() 242 | 243 | } 244 | 245 | // MARK: - Data validation 246 | func validateData() -> Bool { 247 | var sourceDict:Dictionary 248 | if !isShippingAddress { 249 | // Check email address too... 250 | if emailAddress == nil { 251 | // no email - warn and give up! 252 | AlertDialog.showAlert("Error", message: "No email address entered! Please enter a valid email and try again.", viewController: self) 253 | 254 | return false 255 | } 256 | 257 | sourceDict = billingDictionary! 258 | } else { 259 | sourceDict = shippingDictionary! 260 | } 261 | 262 | let requiredFields = [contactFirstNameFieldIdentifier, contactLastNameFieldIdentifier, address1FieldIdentifier, cityFieldIdentifier, stateFieldIdentifier, countryFieldIdentifier, postcodeFieldIdentifier] 263 | 264 | var valid = true 265 | 266 | for field in requiredFields { 267 | var valuePresent = false 268 | var lengthValid = false 269 | 270 | if let value = sourceDict[field] { 271 | // success 272 | valuePresent = true 273 | let stringValue = value as String 274 | if stringValue.characters.count < 1 { 275 | // The string's empty! 276 | lengthValid = true 277 | } 278 | continue 279 | } else { 280 | valuePresent = false 281 | } 282 | 283 | if !valuePresent || !lengthValid { 284 | // Warn user! 285 | valid = false 286 | 287 | var userPresentableName = field.replacingOccurrences(of: "_", with: " ") 288 | userPresentableName = userPresentableName.capitalized 289 | 290 | AlertDialog.showAlert("Error", message: "\(userPresentableName) is not present", viewController: self) 291 | } 292 | } 293 | 294 | return valid 295 | 296 | } 297 | 298 | // MARK: - Data processing 299 | 300 | // A function that gets all of the address field values and returns a billing or shipping address dictionary suitable to pass to the Moltin API. 301 | func getAddressDict() -> Dictionary { 302 | var sourceDict:Dictionary 303 | if !isShippingAddress { 304 | sourceDict = billingDictionary! 305 | } else { 306 | sourceDict = shippingDictionary! 307 | } 308 | 309 | var country = sourceDict[countryFieldIdentifier] 310 | // Perform a country code lookup 311 | if countryArray != nil { 312 | country = countryArray![selectedCountryIndex!]["code"] 313 | 314 | } 315 | 316 | var formattedDict = Dictionary() 317 | formattedDict[contactFirstNameFieldIdentifier] = sourceDict[contactFirstNameFieldIdentifier] 318 | formattedDict[contactLastNameFieldIdentifier] = sourceDict[contactLastNameFieldIdentifier] 319 | formattedDict[address1FieldIdentifier] = sourceDict[address1FieldIdentifier] 320 | 321 | // Concatenate together Address 2... 322 | var address2 = "" 323 | if (formattedDict[address2FieldIdentifier] != nil) { 324 | // There's a value in address 2 325 | address2 = formattedDict[address2FieldIdentifier]! 326 | 327 | } 328 | 329 | // Add on city 330 | address2 = address2 + ", " + sourceDict[cityFieldIdentifier]! 331 | 332 | // Add on state 333 | address2 = address2 + ", " + sourceDict[stateFieldIdentifier]! 334 | 335 | 336 | formattedDict[countryFieldIdentifier] = country 337 | formattedDict[postcodeFieldIdentifier] = sourceDict[postcodeFieldIdentifier] 338 | 339 | return formattedDict 340 | 341 | } 342 | 343 | //MARK: - Text field Cell Delegate 344 | func textEnteredInCell(_ cell: TextEntryTableViewCell, cellId:String, text: String) { 345 | let cellId = cell.cellId! 346 | 347 | if cellId == contactEmailFieldIdentifier { 348 | emailAddress = text 349 | return 350 | } 351 | 352 | if isShippingAddress { 353 | shippingDictionary?[cellId] = text 354 | } else { 355 | billingDictionary?[cellId] = text 356 | } 357 | } 358 | 359 | //MARK: - Switch Cell Delegate 360 | func switchCellSwitched(_ cell: SwitchTableViewCell, status: Bool) { 361 | // User has selected to use the same shipping address as billing address. 362 | useSameShippingAddress = status 363 | 364 | } 365 | 366 | //MARK: - Continue Button 367 | fileprivate func continueButtonTapped() { 368 | // If this is the billing address screen, see if the user wants to enter a seperate shipping address... 369 | // If they do, transition to the shipping address entry screen 370 | // If they don't - or this is the shipping address screen - carry on with the order... 371 | 372 | 373 | // First, check the data entered is valid - if it isn't don't bother. 374 | if !validateData() { 375 | return 376 | } 377 | 378 | if isShippingAddress { 379 | performSegue(withIdentifier: SHIPPING_ADDRESS_SHIPPING_SEGUE, sender: self) 380 | } else { 381 | if useSameShippingAddress { 382 | // They wanna use the current billing address as the shipping address too, so we need to segue to the shipping method choice view, since we know all details now. 383 | performSegue(withIdentifier: BILLING_ADDRESS_SHIPPING_SEGUE, sender: self) 384 | } else { 385 | performSegue(withIdentifier: SHIPPING_ADDRESS_SEGUE, sender: self) 386 | 387 | } 388 | 389 | } 390 | 391 | 392 | 393 | } 394 | 395 | 396 | 397 | // MARK: - Navigation 398 | 399 | // In a storyboard-based application, you will often want to do a little preparation before navigation 400 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 401 | // Get the new view controller using [segue destinationViewController]. 402 | // Pass the selected object to the new view controller. 403 | 404 | 405 | // (initialising these to blanks here to silence Swift's warnings in the segue preperation later on - it doesn't trust that these have been initialised when they in fact WILL have by the time that segue is ever used). 406 | var billingDict = Dictionary() 407 | var shippingDict = Dictionary() 408 | 409 | if isShippingAddress { 410 | billingDict = billingDictionary! 411 | shippingDict = getAddressDict() 412 | } else { 413 | billingDict = getAddressDict() 414 | 415 | 416 | } 417 | 418 | if useSameShippingAddress { 419 | shippingDict = billingDict 420 | } 421 | 422 | 423 | if segue.identifier == SHIPPING_ADDRESS_SHIPPING_SEGUE || segue.identifier == BILLING_ADDRESS_SHIPPING_SEGUE { 424 | // Set up the shipping address view's address variables... 425 | let newViewController = segue.destination as! ShippingTableViewController 426 | newViewController.billingDictionary = billingDict 427 | newViewController.shippingDictionary = shippingDict 428 | 429 | print("shippingDict = \(shippingDict)") 430 | 431 | newViewController.emailAddress = emailAddress! 432 | } 433 | 434 | if segue.identifier == SHIPPING_ADDRESS_SEGUE { 435 | // We're seguing to another AddressEntryTableViewController instance, let's let it know that it's for shipping address entry, and that it has a billing address already... 436 | let newViewController = segue.destination as! AddressEntryTableViewController 437 | newViewController.isShippingAddress = true 438 | newViewController.billingDictionary = billingDict 439 | newViewController.countryArray = countryArray! 440 | newViewController.emailAddress = emailAddress! 441 | } 442 | 443 | } 444 | 445 | } 446 | -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample/AlertDialog.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlertDialog.swift 3 | // MoltinSwiftExample 4 | // 5 | // Created by Dylan McKee on 18/08/2015. 6 | // Copyright (c) 2015 Moltin. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | // A simple convenience class to present alerts, to avoid lots of UIAlertController code duplication. 13 | class AlertDialog { 14 | 15 | class func showAlert(_ title: String, message: String, viewController: UIViewController) { 16 | 17 | let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) 18 | 19 | let dismissAction = UIAlertAction(title: "OK", style: .cancel, handler: nil) 20 | alertController.addAction(dismissAction) 21 | 22 | viewController.present(alertController, animated: true, completion: nil) 23 | 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // MoltinSwiftExample 4 | // 5 | // Created by Dylan McKee on 15/08/2015. 6 | // Copyright (c) 2015 Moltin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Moltin 11 | 12 | // Declare some global constants to make them easily accessible in other classes. 13 | 14 | let MOLTIN_STORE_ID = "umRG34nxZVGIuCSPfYf8biBSvtABgTR8GMUtflyE" 15 | 16 | let MOLTIN_LOGGING = true 17 | 18 | // RGB: 139, 98, 181 19 | let MOLTIN_COLOR = UIColor(red: (139.0/255.0), green: (98.0/255.0), blue: (181.0/255.0), alpha: 1.0) 20 | 21 | 22 | @UIApplicationMain 23 | class AppDelegate: UIResponder, UIApplicationDelegate { 24 | 25 | var window: UIWindow? 26 | 27 | 28 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 29 | // Override point for customization after application launch. 30 | // Set the window's tint color to the Moltin color 31 | self.window?.tintColor = MOLTIN_COLOR 32 | 33 | // Initialise the Moltin SDK with our store ID. 34 | Moltin.sharedInstance().setPublicId(MOLTIN_STORE_ID) 35 | 36 | // Do you want the Moltin SDK to log API calls? (This should probably be false in production apps...) 37 | Moltin.sharedInstance().setLoggingEnabled(MOLTIN_LOGGING) 38 | 39 | 40 | return true 41 | } 42 | 43 | func applicationWillResignActive(_ application: UIApplication) { 44 | // 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. 45 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 46 | } 47 | 48 | func applicationDidEnterBackground(_ application: UIApplication) { 49 | // 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. 50 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 51 | } 52 | 53 | func applicationWillEnterForeground(_ application: UIApplication) { 54 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 55 | } 56 | 57 | func applicationDidBecomeActive(_ application: UIApplication) { 58 | // 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. 59 | } 60 | 61 | func applicationWillTerminate(_ application: UIApplication) { 62 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 63 | } 64 | 65 | func switchToCartTab() { 66 | let tabBarController = self.window!.rootViewController as! UITabBarController 67 | tabBarController.selectedIndex = 1 68 | 69 | } 70 | 71 | 72 | } 73 | 74 | -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 130 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. 214 | 215 | 216 | 217 | 218 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 473 | 483 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 691 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 772 | 773 | 774 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | 790 | 791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 | 800 | 801 | -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample/CartTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CartTableViewCell.swift 3 | // MoltinSwiftExample 4 | // 5 | // Created by Dylan McKee on 16/08/2015. 6 | // Copyright (c) 2015 Moltin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol CartTableViewCellDelegate { 12 | func cartTableViewCellSetQuantity(_ cell: CartTableViewCell, quantity: Int) 13 | } 14 | 15 | class CartTableViewCell: UITableViewCell { 16 | 17 | @IBOutlet weak var itemImageView:UIImageView? 18 | @IBOutlet weak var itemTitleLabel:UILabel? 19 | @IBOutlet weak var itemPriceLabel:UILabel? 20 | @IBOutlet weak var itemQuantityLabel:UILabel? 21 | @IBOutlet weak var itemQuantityStepper:UIStepper? 22 | 23 | var delegate:CartTableViewCellDelegate? 24 | 25 | var productId:String? 26 | 27 | var quantity:Int { 28 | get { 29 | if (self.itemQuantityStepper != nil) { 30 | return Int(self.itemQuantityStepper!.value) 31 | } 32 | 33 | return 0 34 | } 35 | 36 | set { 37 | self.setItemQuantity(quantity) 38 | } 39 | 40 | } 41 | 42 | override func awakeFromNib() { 43 | super.awakeFromNib() 44 | // Initialization code 45 | } 46 | 47 | override func setSelected(_ selected: Bool, animated: Bool) { 48 | super.setSelected(selected, animated: animated) 49 | 50 | // Configure the view for the selected state 51 | } 52 | 53 | func setItemDictionary(_ itemDict: NSDictionary) { 54 | 55 | itemTitleLabel?.text = itemDict.value(forKey: "title") as? String 56 | 57 | itemPriceLabel?.text = itemDict.value(forKeyPath: "totals.post_discount.formatted.with_tax") as? String 58 | 59 | if let qty:NSNumber = itemDict.value(forKeyPath: "quantity") as? NSNumber { 60 | _ = "Qty. \(qty.intValue)" 61 | self.itemQuantityStepper?.value = qty.doubleValue 62 | } 63 | 64 | 65 | 66 | var imageUrl = "" 67 | 68 | if let images = itemDict.object(forKey: "images") as? NSArray { 69 | if (images.firstObject != nil) { 70 | imageUrl = (images.firstObject as! NSDictionary).value(forKeyPath: "url.https") as! String 71 | } 72 | 73 | } 74 | 75 | itemImageView?.sd_setImage(with: URL(string: imageUrl)) 76 | } 77 | 78 | @IBAction func stepperValueChanged(_ sender: AnyObject){ 79 | let value = Int(itemQuantityStepper!.value) 80 | 81 | setItemQuantity(value) 82 | 83 | } 84 | 85 | func setItemQuantity(_ quantity: Int) { 86 | let itemQuantityText = "Qty. \(quantity)" 87 | itemQuantityLabel?.text = itemQuantityText 88 | 89 | itemQuantityStepper?.value = Double(quantity) 90 | 91 | // Notify delegate, if there is one, too... 92 | if (delegate != nil) { 93 | delegate?.cartTableViewCellSetQuantity(self, quantity: quantity) 94 | } 95 | 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample/CartViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CartViewController.swift 3 | // MoltinSwiftExample 4 | // 5 | // Created by Dylan McKee on 15/08/2015. 6 | // Copyright (c) 2015 Moltin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Moltin 11 | 12 | class CartViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, CartTableViewCellDelegate { 13 | 14 | fileprivate let CART_CELL_REUSE_IDENTIFIER = "CartTableViewCell" 15 | 16 | @IBOutlet weak var tableView:UITableView? 17 | @IBOutlet weak var totalLabel:UILabel? 18 | @IBOutlet weak var checkoutButton:UIButton? 19 | 20 | fileprivate var cartData:NSDictionary? 21 | fileprivate var cartProducts:NSDictionary? 22 | 23 | fileprivate let BILLING_ADDRESS_SEGUE_IDENTIFIER = "showBillingAddress" 24 | 25 | override func viewDidLoad() { 26 | super.viewDidLoad() 27 | // Do any additional setup after loading the view, typically from a nib. 28 | self.title = "Cart" 29 | 30 | totalLabel?.text = "" 31 | 32 | 33 | } 34 | 35 | override func viewWillAppear(_ animated: Bool) { 36 | super.viewWillAppear(animated) 37 | 38 | 39 | refreshCart() 40 | 41 | } 42 | 43 | override func didReceiveMemoryWarning() { 44 | super.didReceiveMemoryWarning() 45 | // Dispose of any resources that can be recreated. 46 | } 47 | 48 | func refreshCart() { 49 | SwiftSpinner.show("Updating cart") 50 | 51 | // Get the cart contents from Moltin API 52 | Moltin.sharedInstance().cart.getContentsWithsuccess({ (response) -> Void in 53 | // Got cart contents succesfully! 54 | // Set local var's 55 | self.cartData = response as NSDictionary? 56 | //println(self.cartData) 57 | 58 | self.cartProducts = self.cartData?.value(forKeyPath: "result.contents") as? NSDictionary 59 | 60 | // Reset cart total 61 | if let cartPriceString:NSString = self.cartData?.value(forKeyPath: "result.totals.post_discount.formatted.with_tax") as? NSString { 62 | self.totalLabel?.text = cartPriceString as String 63 | 64 | } 65 | 66 | // And reload table of cart items... 67 | self.tableView?.reloadData() 68 | 69 | // Hide loading UI 70 | SwiftSpinner.hide() 71 | 72 | // If there's < 1 product in the cart, disable the checkout button 73 | self.checkoutButton?.isEnabled = (self.cartProducts != nil && (self.cartProducts?.count)! > 0) 74 | 75 | }, failure: { (response, error) -> Void in 76 | // Something went wrong; hide loading UI and warn user 77 | SwiftSpinner.hide() 78 | 79 | AlertDialog.showAlert("Error", message: "Couldn't load cart", viewController: self) 80 | print("Something went wrong...") 81 | print(error) 82 | }) 83 | 84 | 85 | 86 | } 87 | 88 | // MARK: - TableView Data source & Delegate 89 | 90 | func numberOfSections(in tableView: UITableView) -> Int { 91 | return 1 92 | } 93 | 94 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 95 | if (cartProducts != nil) { 96 | return cartProducts!.allKeys.count 97 | } 98 | 99 | 100 | return 0 101 | 102 | } 103 | 104 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 105 | let cell = tableView.dequeueReusableCell(withIdentifier: CART_CELL_REUSE_IDENTIFIER, for: indexPath) as! CartTableViewCell 106 | 107 | let row = (indexPath as NSIndexPath).row 108 | 109 | let product:NSDictionary = cartProducts!.allValues[row] as! NSDictionary 110 | 111 | cell.setItemDictionary(product) 112 | 113 | cell.productId = cartProducts!.allKeys[row] as? String 114 | 115 | cell.delegate = self 116 | 117 | 118 | return cell 119 | } 120 | 121 | 122 | 123 | func tableView(_ _tableView: UITableView, 124 | willDisplay cell: UITableViewCell, 125 | forRowAt indexPath: IndexPath) { 126 | 127 | if cell.responds(to: #selector(setter: UITableViewCell.separatorInset)) { 128 | cell.separatorInset = UIEdgeInsets.zero 129 | } 130 | if cell.responds(to: #selector(setter: UIView.layoutMargins)) { 131 | cell.layoutMargins = UIEdgeInsets.zero 132 | } 133 | if cell.responds(to: #selector(setter: UIView.preservesSuperviewLayoutMargins)) { 134 | cell.preservesSuperviewLayoutMargins = false 135 | } 136 | } 137 | 138 | func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { 139 | if (editingStyle == UITableViewCellEditingStyle.delete){ 140 | // Remove the item from the cart. 141 | removeItemFromCartAtIndex((indexPath as NSIndexPath).row) 142 | } 143 | } 144 | 145 | fileprivate func removeItemFromCartAtIndex(_ index: Int) { 146 | // Get item ID... 147 | let selectedProductId = cartProducts!.allKeys[index] as? String 148 | 149 | SwiftSpinner.show("Updating cart") 150 | 151 | 152 | // And remove it from the cart... 153 | Moltin.sharedInstance().cart.removeItem(withId: selectedProductId, success: { (response) -> Void in 154 | // Completed item removal - refresh cart hide loading UI 155 | self.refreshCart() 156 | 157 | SwiftSpinner.hide() 158 | 159 | 160 | }, failure: { (response, error) -> Void in 161 | // Removal failed - hide loading UI and warn the user 162 | SwiftSpinner.hide() 163 | 164 | AlertDialog.showAlert("Error", message: "Couldn't update cart", viewController: self) 165 | print("Something went wrong...") 166 | print(error) 167 | }) 168 | } 169 | 170 | // MARK: - Cell delegate 171 | func cartTableViewCellSetQuantity(_ cell: CartTableViewCell, quantity: Int) { 172 | // The cell's quantity's been updated by the stepper control - tell the Moltin API and refresh the cart too. 173 | // If quantity is zero, the Moltin API automagically knows to remove the item from the cart 174 | 175 | // Loading UI.. 176 | SwiftSpinner.show("Updating quantity") 177 | 178 | // Update to new quantity value... 179 | Moltin.sharedInstance().cart.updateItem(withId: cell.productId!, parameters: ["quantity": quantity], success: { (response) -> Void in 180 | // Update succesful, refresh cart 181 | self.refreshCart() 182 | 183 | SwiftSpinner.hide() 184 | 185 | 186 | }, failure: { (response, error) -> Void in 187 | // Something went wrong; hide loading UI and warn user 188 | SwiftSpinner.hide() 189 | 190 | AlertDialog.showAlert("Error", message: "Couldn't update cart", viewController: self) 191 | print("Something went wrong...") 192 | print(error) 193 | }) 194 | 195 | 196 | } 197 | 198 | // MARK: - Checkout button 199 | @IBAction func checkoutButtonClicked(_ sender: AnyObject) { 200 | performSegue(withIdentifier: BILLING_ADDRESS_SEGUE_IDENTIFIER, sender: self) 201 | } 202 | 203 | } 204 | 205 | -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample/CollectionTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionTableViewCell.swift 3 | // MoltinSwiftExample 4 | // 5 | // Created by Dylan McKee on 15/08/2015. 6 | // Copyright (c) 2015 Moltin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SDWebImage 11 | 12 | class CollectionTableViewCell: UITableViewCell { 13 | 14 | @IBOutlet weak var collectionLabel:UILabel? 15 | @IBOutlet weak var collectionImage:UIImageView? 16 | 17 | 18 | override func awakeFromNib() { 19 | super.awakeFromNib() 20 | // Initialization code 21 | } 22 | 23 | override func setSelected(_ selected: Bool, animated: Bool) { 24 | super.setSelected(selected, animated: animated) 25 | 26 | // Configure the view for the selected state 27 | } 28 | 29 | func setCollectionDictionary(_ dict: NSDictionary) { 30 | // Set up the cell based on the values of the dictionary that we've been passed 31 | 32 | collectionLabel?.text = dict.value(forKey: "title") as? String 33 | 34 | // Extract image URL and set that too... 35 | var imageUrl = "" 36 | 37 | if let images = dict.value(forKey: "images") as? NSArray { 38 | if (images.firstObject != nil) { 39 | imageUrl = (images.firstObject as! NSDictionary).value(forKeyPath: "url.https") as! String 40 | } 41 | } 42 | 43 | collectionImage?.sd_setImage(with: URL(string: imageUrl)) 44 | 45 | 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample/CollectionsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionsViewController.swift 3 | // MoltinSwiftExample 4 | // 5 | // Created by Dylan McKee on 15/08/2015. 6 | // Copyright (c) 2015 Moltin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Moltin 11 | 12 | class CollectionsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { 13 | 14 | @IBOutlet weak var tableView:UITableView? 15 | 16 | fileprivate var collections:NSArray? 17 | 18 | fileprivate let COLLECTION_CELL_REUSE_IDENTIFIER = "CollectionCell" 19 | 20 | fileprivate let PRODUCTS_LIST_SEGUE_IDENTIFIER = "productsListSegue" 21 | 22 | fileprivate var selectedCollectionDict:NSDictionary? 23 | 24 | 25 | override func viewDidLoad() { 26 | super.viewDidLoad() 27 | // Do any additional setup after loading the view, typically from a nib. 28 | self.title = "Collections" 29 | 30 | // Show loading UI 31 | SwiftSpinner.show("Loading Collections") 32 | 33 | // Get collections, async 34 | Moltin.sharedInstance().collection.listing(withParameters: ["status": NSNumber(value: 1), "limit": NSNumber(value: 20)], success: { (response) -> Void in 35 | // We have collections - show them! 36 | SwiftSpinner.hide() 37 | 38 | self.collections = response?["result"] as? NSArray 39 | 40 | self.tableView?.reloadData() 41 | 42 | }) { (response, error) -> Void in 43 | // Something went wrong; hide loading UI and display warning. 44 | SwiftSpinner.hide() 45 | 46 | AlertDialog.showAlert("Error", message: "Couldn't load collections", viewController: self) 47 | print("Something went wrong...") 48 | print(error) 49 | } 50 | 51 | 52 | } 53 | 54 | 55 | // MARK: - TableView Data source & Delegate 56 | 57 | func numberOfSections(in tableView: UITableView) -> Int { 58 | return 1 59 | } 60 | 61 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 62 | if collections != nil { 63 | return collections!.count 64 | } 65 | 66 | return 0 67 | } 68 | 69 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 70 | let cell = tableView.dequeueReusableCell(withIdentifier: COLLECTION_CELL_REUSE_IDENTIFIER, for: indexPath) as! CollectionTableViewCell 71 | 72 | let row = (indexPath as NSIndexPath).row 73 | 74 | let collectionDictionary = collections?.object(at: row) as! NSDictionary 75 | 76 | cell.setCollectionDictionary(collectionDictionary) 77 | 78 | 79 | 80 | return cell 81 | } 82 | 83 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 84 | tableView.deselectRow(at: indexPath, animated: true) 85 | 86 | selectedCollectionDict = collections?.object(at: (indexPath as NSIndexPath).row) as? NSDictionary 87 | 88 | performSegue(withIdentifier: PRODUCTS_LIST_SEGUE_IDENTIFIER, sender: self) 89 | 90 | 91 | } 92 | 93 | func tableView(_ _tableView: UITableView, 94 | willDisplay cell: UITableViewCell, 95 | forRowAt indexPath: IndexPath) { 96 | 97 | if cell.responds(to: #selector(setter: UITableViewCell.separatorInset)) { 98 | cell.separatorInset = UIEdgeInsets.zero 99 | } 100 | if cell.responds(to: #selector(setter: UIView.layoutMargins)) { 101 | cell.layoutMargins = UIEdgeInsets.zero 102 | } 103 | if cell.responds(to: #selector(setter: UIView.preservesSuperviewLayoutMargins)) { 104 | cell.preservesSuperviewLayoutMargins = false 105 | } 106 | } 107 | 108 | // MARK: - Navigation 109 | 110 | // In a storyboard-based application, you will often want to do a little preparation before navigation 111 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 112 | // Get the new view controller using [segue destinationViewController]. 113 | // Pass the selected object to the new view controller. 114 | 115 | if segue.identifier == PRODUCTS_LIST_SEGUE_IDENTIFIER { 116 | // Set up products list view! 117 | let newViewController = segue.destination as! ProductListTableViewController 118 | 119 | newViewController.title = selectedCollectionDict!.value(forKey: "title") as? String 120 | newViewController.collectionId = selectedCollectionDict!.value(forKeyPath: "id") as? String 121 | 122 | } 123 | 124 | } 125 | 126 | override func didReceiveMemoryWarning() { 127 | super.didReceiveMemoryWarning() 128 | // Dispose of any resources that can be recreated. 129 | } 130 | 131 | 132 | } 133 | 134 | -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample/ContinueButtonTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContinueButtonTableViewCell.swift 3 | // MoltinSwiftExample 4 | // 5 | // Created by Dylan McKee on 18/08/2015. 6 | // Copyright (c) 2015 Moltin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | let CONTINUE_BUTTON_CELL_IDENTIFIER = "continueButtonCell" 12 | 13 | class ContinueButtonTableViewCell: UITableViewCell { 14 | 15 | override func awakeFromNib() { 16 | super.awakeFromNib() 17 | // Initialization code 18 | } 19 | 20 | override func setSelected(_ selected: Bool, animated: Bool) { 21 | super.setSelected(selected, animated: animated) 22 | 23 | // Configure the view for the selected state 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample/DataEntryTextField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DateEntryTextField.swift 3 | // MoltinSwiftExample 4 | // 5 | // Created by Dylan McKee on 21/08/2015. 6 | // Copyright (c) 2015 Moltin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DataEntryTextField: UITextField { 12 | 13 | /* 14 | // Only override drawRect: if you perform custom drawing. 15 | // An empty implementation adversely affects performance during animation. 16 | override func drawRect(rect: CGRect) { 17 | // Drawing code 18 | } 19 | */ 20 | 21 | func setDoneInputAccessoryView() { 22 | let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: self.frame.size.width, height: 44)) 23 | 24 | let space = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.flexibleSpace, target: self, action: nil) 25 | 26 | let doneButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.done, target: self, action: #selector(DataEntryTextField.btnDoneTap(_:))) 27 | 28 | 29 | toolbar.setItems([space, doneButton], animated: true) 30 | 31 | self.inputAccessoryView = toolbar 32 | 33 | } 34 | 35 | func btnDoneTap(_ sender: AnyObject) { 36 | resignFirstResponder() 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "size" : "29x29", 15 | "idiom" : "iphone", 16 | "filename" : "Icon-Small@2x.png", 17 | "scale" : "2x" 18 | }, 19 | { 20 | "size" : "29x29", 21 | "idiom" : "iphone", 22 | "filename" : "Icon-Small@3x.png", 23 | "scale" : "3x" 24 | }, 25 | { 26 | "size" : "40x40", 27 | "idiom" : "iphone", 28 | "filename" : "Icon-Small-40@2x.png", 29 | "scale" : "2x" 30 | }, 31 | { 32 | "size" : "40x40", 33 | "idiom" : "iphone", 34 | "filename" : "Icon-Small-40@3x.png", 35 | "scale" : "3x" 36 | }, 37 | { 38 | "size" : "60x60", 39 | "idiom" : "iphone", 40 | "filename" : "Icon-60@2x.png", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "size" : "60x60", 45 | "idiom" : "iphone", 46 | "filename" : "Icon-60@3x.png", 47 | "scale" : "3x" 48 | } 49 | ], 50 | "info" : { 51 | "version" : 1, 52 | "author" : "xcode" 53 | } 54 | } -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moltin/ios-swift-example/663dcaa9c3544c271aa2a1f1e0664731b28e241e/MoltinSwiftExample/MoltinSwiftExample/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moltin/ios-swift-example/663dcaa9c3544c271aa2a1f1e0664731b28e241e/MoltinSwiftExample/MoltinSwiftExample/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample/Images.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moltin/ios-swift-example/663dcaa9c3544c271aa2a1f1e0664731b28e241e/MoltinSwiftExample/MoltinSwiftExample/Images.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample/Images.xcassets/AppIcon.appiconset/Icon-Small-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moltin/ios-swift-example/663dcaa9c3544c271aa2a1f1e0664731b28e241e/MoltinSwiftExample/MoltinSwiftExample/Images.xcassets/AppIcon.appiconset/Icon-Small-40@3x.png -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moltin/ios-swift-example/663dcaa9c3544c271aa2a1f1e0664731b28e241e/MoltinSwiftExample/MoltinSwiftExample/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample/Images.xcassets/AppIcon.appiconset/Icon-Small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moltin/ios-swift-example/663dcaa9c3544c271aa2a1f1e0664731b28e241e/MoltinSwiftExample/MoltinSwiftExample/Images.xcassets/AppIcon.appiconset/Icon-Small@3x.png -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample/Images.xcassets/first.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "collection_stack.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample/Images.xcassets/first.imageset/collection_stack.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moltin/ios-swift-example/663dcaa9c3544c271aa2a1f1e0664731b28e241e/MoltinSwiftExample/MoltinSwiftExample/Images.xcassets/first.imageset/collection_stack.pdf -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample/Images.xcassets/second.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "cart.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample/Images.xcassets/second.imageset/cart.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moltin/ios-swift-example/663dcaa9c3544c271aa2a1f1e0664731b28e241e/MoltinSwiftExample/MoltinSwiftExample/Images.xcassets/second.imageset/cart.pdf -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | Moltin Swift 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UIStatusBarTintParameters 36 | 37 | UINavigationBar 38 | 39 | Style 40 | UIBarStyleDefault 41 | Translucent 42 | 43 | 44 | 45 | UISupportedInterfaceOrientations 46 | 47 | UIInterfaceOrientationPortrait 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample/PaymentViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PaymentViewController.swift 3 | // MoltinSwiftExample 4 | // 5 | // Created by Dylan McKee on 20/08/2015. 6 | // Copyright (c) 2015 Moltin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Moltin 11 | 12 | class PaymentViewController: UITableViewController, TextEntryTableViewCellDelegate, UIPickerViewDataSource, UIPickerViewDelegate { 13 | 14 | // Replace this constant with your store's payment gateway slug 15 | fileprivate let PAYMENT_GATEWAY = "dummy" 16 | 17 | fileprivate let PAYMENT_METHOD = "purchase" 18 | 19 | // It needs some pass-through variables too... 20 | var emailAddress:String? 21 | var billingDictionary:Dictionary? 22 | var shippingDictionary:Dictionary? 23 | var selectedShippingMethodSlug:String? 24 | fileprivate var cardNumber:String? 25 | fileprivate var cvvNumber:String? 26 | fileprivate var selectedMonth:String? 27 | fileprivate var selectedYear:String? 28 | 29 | fileprivate let CONTINUE_CELL_ROW_INDEX = 3 30 | 31 | fileprivate let cardNumberIdentifier = "cardNumber" 32 | fileprivate let cvvNumberIdentifier = "cvvNumber" 33 | 34 | fileprivate let datePicker = UIPickerView() 35 | fileprivate var monthsArray = Array() 36 | fileprivate var yearsArray = Array() 37 | 38 | // Validation constants 39 | // Apparently, no credit cards have under 12 or over 19 digits... http://validcreditcardnumbers.info/?p=9 40 | let MAX_CVV_LENGTH = 4 41 | let MIN_CARD_LENGTH = 12 42 | let MAX_CARD_LENGTH = 19 43 | 44 | 45 | override func viewDidLoad() { 46 | super.viewDidLoad() 47 | 48 | datePicker.delegate = self 49 | datePicker.dataSource = self 50 | datePicker.backgroundColor = UIColor.white 51 | datePicker.isOpaque = true 52 | 53 | // Populate months 54 | for i in 1...12 { 55 | monthsArray.append(i) 56 | } 57 | 58 | // Populate years 59 | let components = (Calendar.current as NSCalendar).components(NSCalendar.Unit.year, from: Date()) 60 | let currentYear = components.year 61 | let currentShortYear = (NSString(format: "%d", currentYear!).substring(from: 2) as NSString) 62 | selectedYear = String(format: "%d", currentYear!) 63 | 64 | let shortYearNumber = currentShortYear.intValue 65 | let maxYear = shortYearNumber + 5 66 | for i in shortYearNumber...maxYear { 67 | let shortYear = NSString(format: "%d", i) 68 | yearsArray.append(shortYear as String) 69 | } 70 | 71 | } 72 | 73 | fileprivate func jumpToCartView(_ presentSuccess: Bool) { 74 | for controller in self.navigationController!.viewControllers { 75 | if controller is CartViewController { 76 | self.navigationController!.popToViewController(controller , animated: true) 77 | 78 | if presentSuccess { 79 | AlertDialog.showAlert("Order Successful", message: "Your order has been succesful, congratulations", viewController: controller ) 80 | 81 | } 82 | } 83 | } 84 | } 85 | 86 | override func didReceiveMemoryWarning() { 87 | super.didReceiveMemoryWarning() 88 | // Dispose of any resources that can be recreated. 89 | } 90 | 91 | // MARK: - Table view data source 92 | 93 | override func numberOfSections(in tableView: UITableView) -> Int { 94 | // Return the number of sections. 95 | return 1 96 | } 97 | 98 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 99 | // Return the number of rows in the section. 100 | return 4 101 | } 102 | 103 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 104 | if (indexPath as NSIndexPath).row == CONTINUE_CELL_ROW_INDEX { 105 | let cell = tableView.dequeueReusableCell(withIdentifier: CONTINUE_BUTTON_CELL_IDENTIFIER, for: indexPath) as! ContinueButtonTableViewCell 106 | 107 | return cell 108 | } 109 | 110 | let cell = tableView.dequeueReusableCell(withIdentifier: TEXT_ENTRY_CELL_REUSE_IDENTIFIER, for: indexPath) as! TextEntryTableViewCell 111 | 112 | // Configure the cell... 113 | 114 | switch ((indexPath as NSIndexPath).row) { 115 | case 0: 116 | cell.textField?.placeholder = "Card number" 117 | cell.textField?.keyboardType = UIKeyboardType.numberPad 118 | cell.cellId = cardNumberIdentifier 119 | cell.textField?.text = cardNumber 120 | case 1: 121 | cell.textField?.placeholder = "CVV number" 122 | cell.textField?.keyboardType = UIKeyboardType.numberPad 123 | cell.cellId = cvvNumberIdentifier 124 | cell.textField?.text = cvvNumber 125 | case 2: 126 | cell.textField?.placeholder = "Expiry date" 127 | cell.textField?.inputView = datePicker 128 | cell.textField?.setDoneInputAccessoryView() 129 | 130 | cell.cellId = "expiryDate" 131 | 132 | if (selectedYear != nil) && (selectedMonth != nil) { 133 | let shortYearNumber = (selectedYear! as NSString).intValue 134 | let shortYear = (NSString(format: "%d", shortYearNumber).substring(from: 2) as NSString) 135 | let formattedDate = String(format: "%@/%@", selectedMonth!, shortYear) 136 | cell.textField?.text = formattedDate 137 | } 138 | 139 | cell.hideCursor() 140 | default: 141 | cell.textField?.placeholder = "" 142 | 143 | } 144 | 145 | cell.selectionStyle = UITableViewCellSelectionStyle.none 146 | cell.delegate = self 147 | 148 | return cell 149 | } 150 | 151 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 152 | tableView.deselectRow(at: indexPath, animated: true) 153 | 154 | if (indexPath as NSIndexPath).row == CONTINUE_CELL_ROW_INDEX { 155 | // Pay! (after a little validation) 156 | 157 | if validateData() { 158 | completeOrder() 159 | } 160 | 161 | } 162 | } 163 | 164 | //MARK: - Text field Cell Delegate 165 | func textEnteredInCell(_ cell: TextEntryTableViewCell, cellId:String, text: String) { 166 | let cellId = cell.cellId! 167 | 168 | if cellId == cardNumberIdentifier { 169 | cardNumber = text 170 | } 171 | 172 | if cellId == cvvNumberIdentifier { 173 | cvvNumber = text 174 | } 175 | 176 | } 177 | 178 | 179 | //MARK: - Date picker delegate and data source 180 | func numberOfComponents(in pickerView: UIPickerView) -> Int { 181 | return 2 182 | } 183 | 184 | func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { 185 | 186 | if component == 0 { 187 | return monthsArray.count 188 | 189 | } else { 190 | return yearsArray.count 191 | 192 | } 193 | } 194 | 195 | func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { 196 | 197 | if component == 0 { 198 | return String(format: "%d", monthsArray[row]) 199 | 200 | } else { 201 | return yearsArray[row] 202 | 203 | } 204 | 205 | } 206 | 207 | func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { 208 | 209 | if component == 0 { 210 | // Month selected 211 | selectedMonth = String(format: "%d", monthsArray[row]) 212 | 213 | } else { 214 | // Year selected 215 | // WARNING: The following code won't work past year 2100. 216 | selectedYear = "20" + yearsArray[row] 217 | } 218 | 219 | self.tableView.reloadData() 220 | 221 | } 222 | 223 | // MARK: - Data validation 224 | fileprivate func validateData() -> Bool { 225 | // Check CVV is all numeric, and < max length 226 | if cvvNumber == nil || !cvvNumber!.isNumericString() || (cvvNumber!).characters.count > MAX_CVV_LENGTH { 227 | AlertDialog.showAlert("Invalid CVV Number", message: "Please check the CVV number you entered and try again.", viewController: self) 228 | 229 | return false 230 | } 231 | 232 | // Check card number is all numeric, and < max length but also > min length 233 | if cardNumber == nil || !cardNumber!.isNumericString() || (cardNumber!).characters.count > MAX_CARD_LENGTH || (cardNumber!).characters.count < MIN_CARD_LENGTH { 234 | AlertDialog.showAlert("Invalid Card Number", message: "Please check the card number you entered and try again.", viewController: self) 235 | 236 | return false 237 | } 238 | 239 | return true 240 | } 241 | 242 | // MARK: - Moltin Order API 243 | fileprivate func completeOrder() { 244 | 245 | // Show some loading UI... 246 | SwiftSpinner.show("Completing Purchase") 247 | 248 | let firstName = billingDictionary!["first_name"]! as String 249 | let lastName = billingDictionary!["last_name"]! as String 250 | 251 | let orderParameters = [ 252 | "customer": ["first_name": firstName, 253 | "last_name": lastName, 254 | "email": emailAddress!], 255 | "shipping": self.selectedShippingMethodSlug!, 256 | "gateway": PAYMENT_GATEWAY, 257 | "bill_to": self.billingDictionary!, 258 | "ship_to": self.shippingDictionary! 259 | ] as [AnyHashable: Any] 260 | 261 | Moltin.sharedInstance().cart.order(withParameters: orderParameters, success: { (response) -> Void in 262 | // Order succesful 263 | print("Order succeeded: \(response)") 264 | 265 | // Extract the Order ID so that it can be used in payment too... 266 | let orderId = NSDictionary(dictionary: response!).value(forKeyPath: "result.id") as! String 267 | print("Order ID: \(orderId)") 268 | 269 | let paymentParameters = ["data": ["number": self.cardNumber!, 270 | "expiry_month": self.selectedMonth!, 271 | "expiry_year": self.selectedYear!, 272 | "cvv": self.cvvNumber! 273 | ]] as [AnyHashable: Any] 274 | 275 | Moltin.sharedInstance().checkout.payment(withMethod: self.PAYMENT_METHOD, order: orderId, parameters: paymentParameters, success: { (response) -> Void in 276 | // Payment successful... 277 | print("Payment successful: \(response)") 278 | 279 | 280 | SwiftSpinner.hide() 281 | 282 | self.jumpToCartView(true) 283 | 284 | 285 | 286 | 287 | 288 | }) { (response, error) -> Void in 289 | // Payment error 290 | print("Payment error: \(error)") 291 | SwiftSpinner.hide() 292 | AlertDialog.showAlert("Payment Failed", message: "Payment failed - please try again", viewController: self) 293 | 294 | 295 | } 296 | 297 | 298 | }) { (response, error) -> Void in 299 | // Order failed 300 | print("Order error: \(error)") 301 | SwiftSpinner.hide() 302 | 303 | AlertDialog.showAlert("Order Failed", message: "Order failed - please try again", viewController: self) 304 | 305 | } 306 | 307 | 308 | } 309 | 310 | 311 | } 312 | -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample/ProductDetailViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProductDetailViewController.swift 3 | // MoltinSwiftExample 4 | // 5 | // Created by Dylan McKee on 16/08/2015. 6 | // Copyright (c) 2015 Moltin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Moltin 11 | 12 | class ProductDetailViewController: UIViewController { 13 | 14 | var productDict:NSDictionary? 15 | 16 | @IBOutlet weak var descriptionTextView:UITextView? 17 | @IBOutlet weak var productImageView:UIImageView? 18 | @IBOutlet weak var buyButton:UIButton? 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | 23 | buyButton?.backgroundColor = MOLTIN_COLOR 24 | 25 | // Do any additional setup after loading the view. 26 | if let description = productDict!.value(forKey: "description") as? String { 27 | self.descriptionTextView?.text = description 28 | 29 | } 30 | 31 | if let price = productDict!.value(forKeyPath: "price.data.formatted.with_tax") as? String { 32 | let buyButtonTitle = String(format: "Buy Now (%@)", price) 33 | self.buyButton?.setTitle(buyButtonTitle, for: UIControlState()) 34 | } 35 | 36 | var imageUrl = "" 37 | 38 | if let images = productDict!.object(forKey: "images") as? NSArray { 39 | if (images.firstObject != nil) { 40 | imageUrl = (images.firstObject as! NSDictionary).value(forKeyPath: "url.https") as! String 41 | } 42 | 43 | } 44 | 45 | productImageView?.sd_setImage(with: URL(string: imageUrl)) 46 | 47 | 48 | } 49 | 50 | override func didReceiveMemoryWarning() { 51 | super.didReceiveMemoryWarning() 52 | // Dispose of any resources that can be recreated. 53 | } 54 | 55 | @IBAction func buyProduct(_ sender: AnyObject) { 56 | // Add the current product to the cart 57 | let productId:String = productDict!.object(forKey: "id") as! String 58 | 59 | SwiftSpinner.show("Updating cart") 60 | 61 | Moltin.sharedInstance().cart.insertItem(withId: productId, quantity: 1, andModifiersOrNil: nil, success: { (response) -> Void in 62 | // Done. 63 | // Switch to cart... 64 | let appDelegate = UIApplication.shared.delegate as! AppDelegate 65 | appDelegate.switchToCartTab() 66 | 67 | // and hide loading UI 68 | SwiftSpinner.hide() 69 | 70 | 71 | }) { (response, error) -> Void in 72 | // Something went wrong. 73 | // Hide loading UI and display an error to the user. 74 | SwiftSpinner.hide() 75 | 76 | AlertDialog.showAlert("Error", message: "Couldn't add product to the cart", viewController: self) 77 | print("Something went wrong...") 78 | print(error) 79 | } 80 | 81 | } 82 | 83 | 84 | 85 | } 86 | -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample/ProductListTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProductListTableViewController.swift 3 | // MoltinSwiftExample 4 | // 5 | // Created by Dylan McKee on 16/08/2015. 6 | // Copyright (c) 2015 Moltin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Moltin 11 | 12 | class ProductListTableViewController: UITableViewController { 13 | 14 | fileprivate let CELL_REUSE_IDENTIFIER = "ProductCell" 15 | 16 | fileprivate let LOAD_MORE_CELL_IDENTIFIER = "ProductsLoadMoreCell" 17 | 18 | fileprivate let PRODUCT_DETAIL_VIEW_SEGUE_IDENTIFIER = "productDetailSegue" 19 | 20 | fileprivate var products:NSMutableArray = NSMutableArray() 21 | 22 | fileprivate var paginationOffset:Int = 0 23 | 24 | fileprivate var showLoadMore:Bool = true 25 | 26 | fileprivate let PAGINATION_LIMIT:Int = 3 27 | 28 | fileprivate var selectedProductDict:NSDictionary? 29 | 30 | var collectionId:String? 31 | 32 | override func viewDidLoad() { 33 | super.viewDidLoad() 34 | 35 | loadProducts(true) 36 | 37 | } 38 | 39 | fileprivate func loadProducts(_ showLoadingAnimation: Bool){ 40 | assert(collectionId != nil, "Collection ID is required!") 41 | 42 | // Load in the next set of products... 43 | 44 | // Show loading if neccesary? 45 | if showLoadingAnimation { 46 | SwiftSpinner.show("Loading products") 47 | } 48 | 49 | 50 | Moltin.sharedInstance().product.listing(withParameters: ["collection": collectionId!, "limit": NSNumber(value: PAGINATION_LIMIT), "offset": paginationOffset], success: { (response) -> Void in 51 | // Let's use this response! 52 | SwiftSpinner.hide() 53 | 54 | 55 | if let newProducts:NSArray = response?["result"] as? NSArray { 56 | self.products.addObjects(from: newProducts as [AnyObject]) 57 | 58 | } 59 | 60 | 61 | let responseDictionary = NSDictionary(dictionary: response!) 62 | 63 | if let newOffset:NSNumber = responseDictionary.value(forKeyPath: "pagination.offsets.next") as? NSNumber { 64 | self.paginationOffset = newOffset.intValue 65 | 66 | } 67 | 68 | if let totalProducts:NSNumber = responseDictionary.value(forKeyPath: "pagination.total") as? NSNumber { 69 | // If we have all the products already, don't show the 'load more' button! 70 | if totalProducts.intValue >= self.products.count { 71 | self.showLoadMore = false 72 | } 73 | 74 | } 75 | 76 | self.tableView.reloadData() 77 | 78 | }) { (response, error) -> Void in 79 | // Something went wrong! 80 | SwiftSpinner.hide() 81 | 82 | AlertDialog.showAlert("Error", message: "Couldn't load products", viewController: self) 83 | 84 | print("Something went wrong...") 85 | print(error) 86 | } 87 | 88 | } 89 | 90 | override func didReceiveMemoryWarning() { 91 | super.didReceiveMemoryWarning() 92 | // Dispose of any resources that can be recreated. 93 | } 94 | 95 | // MARK: - Table view data source 96 | 97 | override func numberOfSections(in tableView: UITableView) -> Int { 98 | // Return the number of sections. 99 | return 1 100 | } 101 | 102 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 103 | // Return the number of rows in the section. 104 | 105 | if showLoadMore { 106 | return (products.count + 1) 107 | } 108 | 109 | return products.count 110 | } 111 | 112 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 113 | 114 | if (showLoadMore && (indexPath as NSIndexPath).row > (products.count - 1)) { 115 | // it's the last item - show a 'Load more' cell for pagination instead. 116 | let cell = tableView.dequeueReusableCell(withIdentifier: LOAD_MORE_CELL_IDENTIFIER, for: indexPath) as! ProductsLoadMoreTableViewCell 117 | 118 | return cell 119 | } 120 | 121 | let cell = tableView.dequeueReusableCell(withIdentifier: CELL_REUSE_IDENTIFIER, for: indexPath) as! ProductsListTableViewCell 122 | 123 | let row = (indexPath as NSIndexPath).row 124 | 125 | let product:NSDictionary = products.object(at: row) as! NSDictionary 126 | 127 | cell.configureWithProduct(product) 128 | 129 | return cell 130 | } 131 | 132 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 133 | tableView.deselectRow(at: indexPath, animated: true) 134 | 135 | if (showLoadMore && (indexPath as NSIndexPath).row > (products.count - 1)) { 136 | // Load more products! 137 | loadProducts(false) 138 | return 139 | } 140 | 141 | 142 | // Push a product detail view controller for the selected product. 143 | let product:NSDictionary = products.object(at: (indexPath as NSIndexPath).row) as! NSDictionary 144 | selectedProductDict = product 145 | 146 | performSegue(withIdentifier: PRODUCT_DETAIL_VIEW_SEGUE_IDENTIFIER, sender: self) 147 | 148 | } 149 | 150 | override func tableView(_ _tableView: UITableView, 151 | willDisplay cell: UITableViewCell, 152 | forRowAt indexPath: IndexPath) { 153 | 154 | if cell.responds(to: #selector(setter: UITableViewCell.separatorInset)) { 155 | cell.separatorInset = UIEdgeInsets.zero 156 | } 157 | if cell.responds(to: #selector(setter: UIView.layoutMargins)) { 158 | cell.layoutMargins = UIEdgeInsets.zero 159 | } 160 | if cell.responds(to: #selector(setter: UIView.preservesSuperviewLayoutMargins)) { 161 | cell.preservesSuperviewLayoutMargins = false 162 | } 163 | } 164 | 165 | // MARK: - Navigation 166 | 167 | // In a storyboard-based application, you will often want to do a little preparation before navigation 168 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 169 | // Get the new view controller using [segue destinationViewController]. 170 | // Pass the selected object to the new view controller. 171 | 172 | if segue.identifier == PRODUCT_DETAIL_VIEW_SEGUE_IDENTIFIER { 173 | // Set up product detail view 174 | let newViewController = segue.destination as! ProductDetailViewController 175 | 176 | newViewController.title = selectedProductDict!.value(forKey: "title") as? String 177 | newViewController.productDict = selectedProductDict 178 | 179 | } 180 | } 181 | 182 | 183 | } 184 | -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample/ProductsListTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProductsListTableViewCell.swift 3 | // MoltinSwiftExample 4 | // 5 | // Created by Dylan McKee on 16/08/2015. 6 | // Copyright (c) 2015 Moltin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ProductsListTableViewCell: UITableViewCell { 12 | 13 | @IBOutlet weak var productNameLabel:UILabel? 14 | @IBOutlet weak var productPriceLabel:UILabel? 15 | @IBOutlet weak var productImageView:UIImageView? 16 | 17 | override func awakeFromNib() { 18 | super.awakeFromNib() 19 | // Initialization code 20 | } 21 | 22 | override func setSelected(_ selected: Bool, animated: Bool) { 23 | super.setSelected(selected, animated: animated) 24 | 25 | // Configure the view for the selected state 26 | } 27 | 28 | func configureWithProduct(_ productDict: NSDictionary) { 29 | // Setup the cell with information provided in productDict. 30 | productNameLabel?.text = productDict.value(forKey: "title") as? String 31 | 32 | productPriceLabel?.text = productDict.value(forKeyPath: "price.data.formatted.with_tax") as? String 33 | 34 | var imageUrl = "" 35 | 36 | if let images = productDict.object(forKey: "images") as? NSArray { 37 | if (images.firstObject != nil) { 38 | imageUrl = (images.firstObject as! NSDictionary).value(forKeyPath: "url.https") as! String 39 | } 40 | 41 | } 42 | 43 | productImageView?.sd_setImage(with: URL(string: imageUrl)) 44 | 45 | 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample/ProductsLoadMoreTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProductsLoadMoreTableViewCell.swift 3 | // MoltinSwiftExample 4 | // 5 | // Created by Dylan McKee on 16/08/2015. 6 | // Copyright (c) 2015 Moltin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ProductsLoadMoreTableViewCell: UITableViewCell { 12 | 13 | override func awakeFromNib() { 14 | super.awakeFromNib() 15 | // Initialization code 16 | } 17 | 18 | override func setSelected(_ selected: Bool, animated: Bool) { 19 | super.setSelected(selected, animated: animated) 20 | 21 | // Configure the view for the selected state 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample/Project-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Project-Bridging-Header.h 3 | // MoltinSwiftExample 4 | // 5 | // Created by Dylan McKee on 15/08/2015. 6 | // Copyright (c) 2015 Moltin. All rights reserved. 7 | // 8 | 9 | #ifndef MoltinSwiftExample_Project_Bridging_Header_h 10 | #define MoltinSwiftExample_Project_Bridging_Header_h 11 | 12 | // Import the Moltin SDK to expose it to Swift files via 'import Moltin' 13 | #import 14 | 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample/ShippingMethodTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShippingMethodTableViewCell.swift 3 | // MoltinSwiftExample 4 | // 5 | // Created by Dylan McKee on 17/08/2015. 6 | // Copyright (c) 2015 Moltin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ShippingMethodTableViewCell: UITableViewCell { 12 | 13 | @IBOutlet weak var methodNameLabel:UILabel? 14 | @IBOutlet weak var costLabel:UILabel? 15 | 16 | 17 | override func awakeFromNib() { 18 | super.awakeFromNib() 19 | // Initialization code 20 | } 21 | 22 | override func setSelected(_ selected: Bool, animated: Bool) { 23 | super.setSelected(selected, animated: animated) 24 | 25 | // Configure the view for the selected state 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample/ShippingTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShippingTableViewController.swift 3 | // MoltinSwiftExample 4 | // 5 | // Created by Dylan McKee on 17/08/2015. 6 | // Copyright (c) 2015 Moltin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Moltin 11 | 12 | class ShippingTableViewController: UITableViewController { 13 | 14 | fileprivate let SHIPPING_CELL_REUSE_IDENTIFIER = "shippingMethodCell" 15 | fileprivate let PAYMENT_SEGUE = "paymentSegue" 16 | 17 | fileprivate var shippingMethods:NSArray? 18 | 19 | // It needs some pass-through variables too... 20 | var emailAddress:String? 21 | var billingDictionary:Dictionary? 22 | var shippingDictionary:Dictionary? 23 | 24 | fileprivate var selectedShippingMethodSlug = "" 25 | 26 | 27 | override func viewDidLoad() { 28 | super.viewDidLoad() 29 | 30 | 31 | if (shippingMethods == nil) { 32 | // get shipping methods for the shipping address we've been passed... 33 | SwiftSpinner.show("Loading Shipping Methods") 34 | 35 | 36 | Moltin.sharedInstance().cart.checkoutWithsuccess({ (response) -> Void in 37 | // Success - let's extract shipping methods... 38 | SwiftSpinner.hide() 39 | 40 | // Shipping methods are stored in an array at key path result.shipping.methods 41 | 42 | self.shippingMethods = (NSDictionary(dictionary: response!).value(forKeyPath: "result.shipping.methods") as! NSArray) 43 | 44 | self.tableView.reloadData() 45 | 46 | }, failure: { (response, error) -> Void in 47 | // Something went wrong - let's warn the user... 48 | }) 49 | 50 | } 51 | } 52 | 53 | override func didReceiveMemoryWarning() { 54 | super.didReceiveMemoryWarning() 55 | // Dispose of any resources that can be recreated. 56 | } 57 | 58 | // MARK: - Table view data source 59 | 60 | override func numberOfSections(in tableView: UITableView) -> Int { 61 | // Return the number of sections. 62 | return 1 63 | } 64 | 65 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 66 | // Return the number of rows in the section. 67 | 68 | if (shippingMethods != nil) { 69 | return shippingMethods!.count 70 | } 71 | 72 | return 0 73 | } 74 | 75 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 76 | let cell = tableView.dequeueReusableCell(withIdentifier: SHIPPING_CELL_REUSE_IDENTIFIER, for: indexPath) as! ShippingMethodTableViewCell 77 | 78 | // Configure the cell... 79 | let shippingMethod = shippingMethods?.object(at: (indexPath as NSIndexPath).row) as! NSDictionary 80 | cell.methodNameLabel?.text = (shippingMethod.value(forKey: "title") as! String) 81 | cell.costLabel?.text = (shippingMethod.value(forKeyPath: "price.data.formatted.with_tax") as! String) 82 | 83 | return cell 84 | } 85 | 86 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 87 | // User has chosen their shipping method - let's continue the checkout... 88 | let shippingMethod = shippingMethods?.object(at: (indexPath as NSIndexPath).row) as! NSDictionary 89 | selectedShippingMethodSlug = (shippingMethod.value(forKey: "slug") as! String) 90 | 91 | // Continue! 92 | performSegue(withIdentifier: PAYMENT_SEGUE, sender: self) 93 | } 94 | 95 | // MARK: - Navigation 96 | 97 | // In a storyboard-based application, you will often want to do a little preparation before navigation 98 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 99 | // Get the new view controller using [segue destinationViewController]. 100 | // Pass the selected object to the new view controller. 101 | if segue.identifier == PAYMENT_SEGUE { 102 | // Setup payment view... 103 | let paymentView = segue.destination as! PaymentViewController 104 | paymentView.billingDictionary = self.billingDictionary! 105 | paymentView.shippingDictionary = self.shippingDictionary! 106 | paymentView.emailAddress = self.emailAddress! 107 | paymentView.selectedShippingMethodSlug = self.selectedShippingMethodSlug 108 | } 109 | 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample/String+Numeric.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+Numeric.swift 3 | // MoltinSwiftExample 4 | // 5 | // Created by Dylan McKee on 21/08/2015. 6 | // Copyright (c) 2015 Moltin. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension String { 12 | func isNumericString() -> Bool { 13 | 14 | let nonDigitChars = CharacterSet.decimalDigits.inverted 15 | 16 | let string = self as NSString 17 | 18 | if string.rangeOfCharacter(from: nonDigitChars).location == NSNotFound { 19 | // definitely numeric entierly 20 | return true 21 | } 22 | 23 | return false 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample/SwiftSpinner.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015-present Marin Todorov, Underplot ltd. 3 | // This code is distributed under the terms and conditions of the MIT license. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | // 9 | 10 | import UIKit 11 | 12 | open class SwiftSpinner: UIView { 13 | 14 | // MARK: - Singleton 15 | 16 | // 17 | // Access the singleton instance 18 | // 19 | open class var sharedInstance: SwiftSpinner { 20 | struct Singleton { 21 | static let instance = SwiftSpinner(frame: CGRect.zero) 22 | } 23 | return Singleton.instance 24 | } 25 | 26 | // MARK: - Init 27 | 28 | // 29 | // Custom init to build the spinner UI 30 | // 31 | 32 | public override init(frame: CGRect) { 33 | super.init(frame: frame) 34 | 35 | blurEffect = UIBlurEffect(style: blurEffectStyle) 36 | blurView = UIVisualEffectView(effect: blurEffect) 37 | addSubview(blurView) 38 | 39 | vibrancyView = UIVisualEffectView(effect: UIVibrancyEffect(blurEffect: blurEffect)) 40 | addSubview(vibrancyView) 41 | 42 | let titleScale: CGFloat = 0.85 43 | titleLabel.frame.size = CGSize(width: frameSize.width * titleScale, height: frameSize.height * titleScale) 44 | titleLabel.font = defaultTitleFont 45 | titleLabel.numberOfLines = 0 46 | titleLabel.textAlignment = .center 47 | titleLabel.lineBreakMode = .byWordWrapping 48 | titleLabel.adjustsFontSizeToFitWidth = true 49 | titleLabel.textColor = UIColor.white 50 | 51 | 52 | blurView.contentView.addSubview(titleLabel) 53 | blurView.contentView.addSubview(vibrancyView) 54 | 55 | outerCircleView.frame.size = frameSize 56 | 57 | outerCircle.path = UIBezierPath(ovalIn: CGRect(x: 0.0, y: 0.0, width: frameSize.width, height: frameSize.height)).cgPath 58 | outerCircle.lineWidth = 8.0 59 | outerCircle.strokeStart = 0.0 60 | outerCircle.strokeEnd = 0.45 61 | outerCircle.lineCap = kCALineCapRound 62 | outerCircle.fillColor = UIColor.clear.cgColor 63 | outerCircle.strokeColor = UIColor.white.cgColor 64 | outerCircleView.layer.addSublayer(outerCircle) 65 | 66 | outerCircle.strokeStart = 0.0 67 | outerCircle.strokeEnd = 1.0 68 | 69 | blurView.contentView.addSubview(outerCircleView) 70 | 71 | innerCircleView.frame.size = frameSize 72 | 73 | let innerCirclePadding: CGFloat = 12 74 | innerCircle.path = UIBezierPath(ovalIn: CGRect(x: innerCirclePadding, y: innerCirclePadding, width: frameSize.width - 2*innerCirclePadding, height: frameSize.height - 2*innerCirclePadding)).cgPath 75 | innerCircle.lineWidth = 4.0 76 | innerCircle.strokeStart = 0.5 77 | innerCircle.strokeEnd = 0.9 78 | innerCircle.lineCap = kCALineCapRound 79 | innerCircle.fillColor = UIColor.clear.cgColor 80 | innerCircle.strokeColor = UIColor.gray.cgColor 81 | innerCircleView.layer.addSublayer(innerCircle) 82 | 83 | innerCircle.strokeStart = 0.0 84 | innerCircle.strokeEnd = 1.0 85 | 86 | blurView.contentView.addSubview(innerCircleView) 87 | 88 | isUserInteractionEnabled = true 89 | } 90 | 91 | open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { 92 | return self 93 | } 94 | 95 | // MARK: - Public interface 96 | 97 | open lazy var titleLabel = UILabel() 98 | open var subtitleLabel: UILabel? 99 | 100 | // 101 | // Custom superview for the spinner 102 | // 103 | fileprivate static weak var customSuperview: UIView? = nil 104 | fileprivate static func containerView() -> UIView? { 105 | return customSuperview ?? UIApplication.shared.keyWindow 106 | } 107 | open class func useContainerView(_ sv: UIView?) { 108 | customSuperview = sv 109 | } 110 | 111 | // 112 | // Show the spinner activity on screen, if visible only update the title 113 | // 114 | @discardableResult 115 | open class func show(_ title: String, animated: Bool = true) -> SwiftSpinner { 116 | 117 | let spinner = SwiftSpinner.sharedInstance 118 | 119 | spinner.clearTapHandler() 120 | 121 | spinner.updateFrame() 122 | 123 | if spinner.superview == nil { 124 | //show the spinner 125 | spinner.alpha = 0.0 126 | 127 | guard let containerView = containerView() else { 128 | fatalError("\n`UIApplication.keyWindow` is `nil`. If you're trying to show a spinner from your view controller's `viewDidLoad` method, do that from `viewWillAppear` instead. Alternatively use `useContainerView` to set a view where the spinner should show") 129 | } 130 | 131 | containerView.addSubview(spinner) 132 | 133 | UIView.animate(withDuration: 0.33, delay: 0.0, options: .curveEaseOut, animations: { 134 | spinner.alpha = 1.0 135 | }, completion: nil) 136 | 137 | #if os(iOS) 138 | // Orientation change observer 139 | NotificationCenter.default.addObserver( 140 | spinner, 141 | selector: #selector(SwiftSpinner.updateFrame), 142 | name: NSNotification.Name.UIApplicationDidChangeStatusBarOrientation, 143 | object: nil) 144 | #endif 145 | } 146 | 147 | spinner.title = title 148 | spinner.animating = animated 149 | 150 | return spinner 151 | } 152 | 153 | // 154 | // Show the spinner activity on screen with duration, if visible only update the title 155 | // 156 | @discardableResult 157 | open class func show(_ duration: Double, title: String, animated: Bool = true) -> SwiftSpinner { 158 | let spinner = SwiftSpinner.show(title, animated: animated) 159 | spinner.delay(duration) { 160 | SwiftSpinner.hide() 161 | } 162 | return spinner 163 | } 164 | 165 | fileprivate static var delayedTokens = [String]() 166 | 167 | 168 | /// 169 | /// Show the spinner with the outer circle representing progress (0 to 1) 170 | /// 171 | @discardableResult 172 | open class func show(_ progress: Double, title: String) -> SwiftSpinner { 173 | let spinner = SwiftSpinner.show(title, animated: false) 174 | spinner.outerCircle.strokeEnd = CGFloat(progress) 175 | return spinner 176 | } 177 | 178 | // 179 | // Hide the spinner 180 | // 181 | open static var hideCancelsScheduledSpinners = true 182 | open class func hide(_ completion: (() -> Void)? = nil) { 183 | 184 | let spinner = SwiftSpinner.sharedInstance 185 | 186 | NotificationCenter.default.removeObserver(spinner) 187 | if hideCancelsScheduledSpinners { 188 | delayedTokens.removeAll() 189 | } 190 | 191 | DispatchQueue.main.async(execute: { 192 | spinner.clearTapHandler() 193 | 194 | if spinner.superview == nil { 195 | return 196 | } 197 | 198 | UIView.animate(withDuration: 0.33, delay: 0.0, options: .curveEaseOut, animations: { 199 | spinner.alpha = 0.0 200 | }, completion: {_ in 201 | spinner.alpha = 1.0 202 | spinner.removeFromSuperview() 203 | spinner.titleLabel.font = spinner.defaultTitleFont 204 | spinner.titleLabel.text = nil 205 | 206 | completion?() 207 | }) 208 | 209 | spinner.animating = false 210 | }) 211 | } 212 | 213 | // 214 | // Set the default title font 215 | // 216 | public class func setTitleFont(_ font: UIFont?) { 217 | let spinner = SwiftSpinner.sharedInstance 218 | 219 | if let font = font { 220 | spinner.titleLabel.font = font 221 | } else { 222 | spinner.titleLabel.font = spinner.defaultTitleFont 223 | } 224 | } 225 | 226 | // 227 | // The spinner title 228 | // 229 | public var title: String = "" { 230 | didSet { 231 | let spinner = SwiftSpinner.sharedInstance 232 | 233 | guard spinner.animating else { 234 | spinner.titleLabel.transform = CGAffineTransform.identity 235 | spinner.titleLabel.alpha = 1.0 236 | spinner.titleLabel.text = self.title 237 | return 238 | } 239 | 240 | UIView.animate(withDuration: 0.15, delay: 0.0, options: .curveEaseOut, animations: { 241 | spinner.titleLabel.transform = CGAffineTransform(scaleX: 0.75, y: 0.75) 242 | spinner.titleLabel.alpha = 0.2 243 | }, completion: {_ in 244 | spinner.titleLabel.text = self.title 245 | UIView.animate(withDuration: 0.35, delay: 0.0, usingSpringWithDamping: 0.35, initialSpringVelocity: 0.0, options: [], animations: { 246 | spinner.titleLabel.transform = CGAffineTransform.identity 247 | spinner.titleLabel.alpha = 1.0 248 | }, completion: nil) 249 | }) 250 | } 251 | } 252 | 253 | // 254 | // observe the view frame and update the subviews layout 255 | // 256 | open override var frame: CGRect { 257 | didSet { 258 | if frame == CGRect.zero { 259 | return 260 | } 261 | blurView.frame = bounds 262 | vibrancyView.frame = blurView.bounds 263 | titleLabel.center = vibrancyView.center 264 | outerCircleView.center = vibrancyView.center 265 | innerCircleView.center = vibrancyView.center 266 | if let subtitle = subtitleLabel { 267 | subtitle.bounds.size = subtitle.sizeThatFits(bounds.insetBy(dx: 20.0, dy: 0.0).size) 268 | subtitle.center = CGPoint(x: bounds.midX, y: bounds.maxY - subtitle.bounds.midY - subtitle.font.pointSize) 269 | } 270 | } 271 | } 272 | 273 | // 274 | // Start the spinning animation 275 | // 276 | 277 | public var animating: Bool = false { 278 | 279 | willSet (shouldAnimate) { 280 | if shouldAnimate && !animating { 281 | spinInner() 282 | spinOuter() 283 | } 284 | } 285 | 286 | didSet { 287 | // update UI 288 | if animating { 289 | self.outerCircle.strokeStart = 0.0 290 | self.outerCircle.strokeEnd = 0.45 291 | self.innerCircle.strokeStart = 0.5 292 | self.innerCircle.strokeEnd = 0.9 293 | } else { 294 | self.outerCircle.strokeStart = 0.0 295 | self.outerCircle.strokeEnd = 1.0 296 | self.innerCircle.strokeStart = 0.0 297 | self.innerCircle.strokeEnd = 1.0 298 | } 299 | } 300 | } 301 | 302 | // 303 | // Tap handler 304 | // 305 | public func addTapHandler(_ tap: @escaping (()->()), subtitle subtitleText: String? = nil) { 306 | clearTapHandler() 307 | 308 | //vibrancyView.contentView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: Selector("didTapSpinner"))) 309 | tapHandler = tap 310 | 311 | if subtitleText != nil { 312 | subtitleLabel = UILabel() 313 | if let subtitle = subtitleLabel { 314 | subtitle.text = subtitleText 315 | subtitle.font = UIFont(name: defaultTitleFont.familyName, size: defaultTitleFont.pointSize * 0.8) 316 | subtitle.textColor = UIColor.white 317 | subtitle.numberOfLines = 0 318 | subtitle.textAlignment = .center 319 | subtitle.lineBreakMode = .byWordWrapping 320 | subtitle.bounds.size = subtitle.sizeThatFits(bounds.insetBy(dx: 20.0, dy: 0.0).size) 321 | subtitle.center = CGPoint(x: bounds.midX, y: bounds.maxY - subtitle.bounds.midY - subtitle.font.pointSize) 322 | vibrancyView.contentView.addSubview(subtitle) 323 | } 324 | } 325 | } 326 | 327 | open override func touchesBegan(_ touches: Set, with event: UIEvent?) { 328 | super.touchesBegan(touches, with: event) 329 | 330 | if tapHandler != nil { 331 | tapHandler?() 332 | tapHandler = nil 333 | } 334 | } 335 | 336 | public func clearTapHandler() { 337 | isUserInteractionEnabled = false 338 | subtitleLabel?.removeFromSuperview() 339 | tapHandler = nil 340 | } 341 | 342 | // MARK: - Private interface 343 | 344 | // 345 | // layout elements 346 | // 347 | 348 | fileprivate var blurEffectStyle: UIBlurEffectStyle = .dark 349 | fileprivate var blurEffect: UIBlurEffect! 350 | fileprivate var blurView: UIVisualEffectView! 351 | fileprivate var vibrancyView: UIVisualEffectView! 352 | 353 | var defaultTitleFont = UIFont(name: "HelveticaNeue", size: 22.0)! 354 | let frameSize = CGSize(width: 200.0, height: 200.0) 355 | 356 | fileprivate lazy var outerCircleView = UIView() 357 | fileprivate lazy var innerCircleView = UIView() 358 | 359 | fileprivate let outerCircle = CAShapeLayer() 360 | fileprivate let innerCircle = CAShapeLayer() 361 | 362 | required public init?(coder aDecoder: NSCoder) { 363 | fatalError("Not coder compliant") 364 | } 365 | 366 | fileprivate var currentOuterRotation: CGFloat = 0.0 367 | fileprivate var currentInnerRotation: CGFloat = 0.1 368 | 369 | fileprivate func spinOuter() { 370 | 371 | if superview == nil { 372 | return 373 | } 374 | 375 | let duration = Double(Float(arc4random()) / Float(UInt32.max)) * 2.0 + 1.5 376 | let randomRotation = Double(Float(arc4random()) / Float(UInt32.max)) * M_PI_4 + M_PI_4 377 | 378 | //outer circle 379 | UIView.animate(withDuration: duration, delay: 0.0, usingSpringWithDamping: 0.4, initialSpringVelocity: 0.0, options: [], animations: { 380 | self.currentOuterRotation -= CGFloat(randomRotation) 381 | self.outerCircleView.transform = CGAffineTransform(rotationAngle: self.currentOuterRotation) 382 | }, completion: {_ in 383 | let waitDuration = Double(Float(arc4random()) / Float(UInt32.max)) * 1.0 + 1.0 384 | self.delay(waitDuration, completion: { 385 | if self.animating { 386 | self.spinOuter() 387 | } 388 | }) 389 | }) 390 | } 391 | 392 | fileprivate func spinInner() { 393 | if superview == nil { 394 | return 395 | } 396 | 397 | //inner circle 398 | UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.0, options: [], animations: { 399 | self.currentInnerRotation += CGFloat(M_PI_4) 400 | self.innerCircleView.transform = CGAffineTransform(rotationAngle: self.currentInnerRotation) 401 | }, completion: {_ in 402 | self.delay(0.5, completion: { 403 | if self.animating { 404 | self.spinInner() 405 | } 406 | }) 407 | }) 408 | } 409 | 410 | public func updateFrame() { 411 | if let containerView = SwiftSpinner.containerView() { 412 | SwiftSpinner.sharedInstance.frame = containerView.bounds 413 | } 414 | } 415 | 416 | // MARK: - Util methods 417 | 418 | func delay(_ seconds: Double, completion:@escaping ()->()) { 419 | let popTime = DispatchTime.now() + Double(Int64( Double(NSEC_PER_SEC) * seconds )) / Double(NSEC_PER_SEC) 420 | 421 | DispatchQueue.main.asyncAfter(deadline: popTime) { 422 | completion() 423 | } 424 | } 425 | 426 | override open func layoutSubviews() { 427 | super.layoutSubviews() 428 | updateFrame() 429 | } 430 | 431 | // MARK: - Tap handler 432 | fileprivate var tapHandler: (()->())? 433 | func didTapSpinner() { 434 | tapHandler?() 435 | } 436 | } 437 | -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample/SwitchTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwitchTableViewCell.swift 3 | // MoltinSwiftExample 4 | // 5 | // Created by Dylan McKee on 18/08/2015. 6 | // Copyright (c) 2015 Moltin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | let SWITCH_TABLE_CELL_REUSE_IDENTIFIER = "switchCell" 12 | 13 | protocol SwitchTableViewCellDelegate { 14 | func switchCellSwitched(_ cell: SwitchTableViewCell, status: Bool) 15 | } 16 | 17 | class SwitchTableViewCell: UITableViewCell { 18 | var delegate:SwitchTableViewCellDelegate? 19 | @IBOutlet weak var switchLabel:UILabel? 20 | 21 | override func awakeFromNib() { 22 | super.awakeFromNib() 23 | // Initialization code 24 | } 25 | 26 | override func setSelected(_ selected: Bool, animated: Bool) { 27 | super.setSelected(selected, animated: animated) 28 | 29 | // Configure the view for the selected state 30 | } 31 | 32 | @IBAction func switchChanged(_ sender: UISwitch) { 33 | if (delegate != nil) { 34 | delegate!.switchCellSwitched(self, status: sender.isOn) 35 | } 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /MoltinSwiftExample/MoltinSwiftExample/TextEntryTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextEntryTableViewCell.swift 3 | // MoltinSwiftExample 4 | // 5 | // Created by Dylan McKee on 17/08/2015. 6 | // Copyright (c) 2015 Moltin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | let TEXT_ENTRY_CELL_REUSE_IDENTIFIER = "textEntryCell" 12 | 13 | 14 | protocol TextEntryTableViewCellDelegate { 15 | func textEnteredInCell(_ cell: TextEntryTableViewCell, cellId:String, text: String) 16 | } 17 | 18 | class TextEntryTableViewCell: UITableViewCell { 19 | 20 | @IBOutlet weak var textField:DataEntryTextField? 21 | 22 | var cellId:String? 23 | 24 | var delegate:TextEntryTableViewCellDelegate? 25 | 26 | override func awakeFromNib() { 27 | super.awakeFromNib() 28 | // Initialization code 29 | 30 | } 31 | 32 | override func setSelected(_ selected: Bool, animated: Bool) { 33 | super.setSelected(selected, animated: animated) 34 | 35 | // Configure the view for the selected state 36 | } 37 | 38 | @IBAction func textFieldDidEndEditing(_ textField: UITextField) { 39 | if delegate != nil { 40 | delegate!.textEnteredInCell(self, cellId: cellId!, text: textField.text!) 41 | } 42 | 43 | } 44 | 45 | func hideCursor() { 46 | // Set the cursor colour to white in the text field to 'hide' it. 47 | textField?.tintColor = UIColor.clear 48 | } 49 | 50 | 51 | } 52 | -------------------------------------------------------------------------------- /MoltinSwiftExample/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '8.0' 2 | 3 | target 'MoltinSwiftExample' do 4 | pod 'Moltin', :git => 'https://github.com/moltin/ios-sdk.git', :branch => 'master' 5 | pod 'SDWebImage', '~>3.7' 6 | end 7 | 8 | use_frameworks! 9 | -------------------------------------------------------------------------------- /MoltinSwiftExample/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - AFNetworking (3.1.0): 3 | - AFNetworking/NSURLSession (= 3.1.0) 4 | - AFNetworking/Reachability (= 3.1.0) 5 | - AFNetworking/Security (= 3.1.0) 6 | - AFNetworking/Serialization (= 3.1.0) 7 | - AFNetworking/UIKit (= 3.1.0) 8 | - AFNetworking/NSURLSession (3.1.0): 9 | - AFNetworking/Reachability 10 | - AFNetworking/Security 11 | - AFNetworking/Serialization 12 | - AFNetworking/Reachability (3.1.0) 13 | - AFNetworking/Security (3.1.0) 14 | - AFNetworking/Serialization (3.1.0) 15 | - AFNetworking/UIKit (3.1.0): 16 | - AFNetworking/NSURLSession 17 | - Moltin (1.1.5): 18 | - AFNetworking 19 | - SDWebImage (3.8.2): 20 | - SDWebImage/Core (= 3.8.2) 21 | - SDWebImage/Core (3.8.2) 22 | 23 | DEPENDENCIES: 24 | - Moltin (from `https://github.com/moltin/ios-sdk.git`, branch `master`) 25 | - SDWebImage (~> 3.7) 26 | 27 | EXTERNAL SOURCES: 28 | Moltin: 29 | :branch: master 30 | :git: https://github.com/moltin/ios-sdk.git 31 | 32 | CHECKOUT OPTIONS: 33 | Moltin: 34 | :commit: 0e4fe4d764633c90397c8908e5e0038fcfa9a9c4 35 | :git: https://github.com/moltin/ios-sdk.git 36 | 37 | SPEC CHECKSUMS: 38 | AFNetworking: 5e0e199f73d8626b11e79750991f5d173d1f8b67 39 | Moltin: 80fe95499216cf783c183bc56352b6b92a3e4836 40 | SDWebImage: 098e97e6176540799c27e804c96653ee0833d13c 41 | 42 | PODFILE CHECKSUM: 010974f7069915d86f84f0fdfdb9711270bda919 43 | 44 | COCOAPODS: 1.0.0 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ⚠️This example is for Moltin API V1 and no longer maintained. Please see the [Swift SDK](https://github.com/moltin/ios-sdk) for more examples. 2 | 3 | # Moltin Swift Example App 4 | A simple example app that demonstrates how to use the Moltin iOS eCommerce SDK in Swift. 5 | 6 | *Prefer Objective-C? [Check out our Objective-C example app instead](https://github.com/moltin/ios-objc-example)* 7 | 8 | # Getting started 9 | This example app uses [CocoaPods](https://guides.cocoapods.org/using/getting-started.html#getting-started) to manage dependencies. 10 | - Clone the project repository 11 | - Run `pod install` in the `MoltinSwiftExample` directory 12 | - Open the .xcworkspace file 13 | - You're all set. 14 | --------------------------------------------------------------------------------