From 1bb1559afde89bc00792fbf6e779312cd7663ab5 Mon Sep 17 00:00:00 2001 From: Ash Furrow Date: Tue, 4 Oct 2016 15:17:31 -0400 Subject: [PATCH 01/46] [Swift 3] Initial pod update. --- Podfile | 10 +++---- Podfile.lock | 78 +++++++++++++++++++++++++++------------------------- 2 files changed, 45 insertions(+), 43 deletions(-) diff --git a/Podfile b/Podfile index 21ca02be..07304b6c 100644 --- a/Podfile +++ b/Podfile @@ -59,11 +59,11 @@ pod 'XNGMarkdownParser' # Swift pods pod 'SwiftyJSON' -pod 'RxSwift' -pod 'RxCocoa' -pod 'Moya/RxSwift' +pod 'RxSwift', '3.0.0-beta.2' +pod 'RxCocoa', '3.0.0-beta.2' +pod 'Moya/RxSwift', '8.0.0-beta.2' pod 'NSObject+Rx' -pod 'Action' +pod 'Action', '2.0.0-beta.1' target 'KioskTests' do @@ -73,6 +73,6 @@ target 'KioskTests' do pod 'Quick' pod 'Nimble' pod 'Forgeries' - pod 'RxBlocking' + pod 'RxBlocking', '3.0.0-beta.2' end diff --git a/Podfile.lock b/Podfile.lock index ee2a529b..6383b916 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,8 +1,8 @@ PODS: - - Action (1.1.0): - - RxCocoa (~> 2.0.0) - - RxSwift (~> 2.0.0) - - Alamofire (3.4.1) + - Action (2.0.0-beta.1): + - RxCocoa + - RxSwift + - Alamofire (4.0.1) - ARAnalytics/CoreIOS (3.8.0) - ARAnalytics/HockeyApp (3.8.0): - ARAnalytics/CoreIOS @@ -35,20 +35,22 @@ PODS: - FBSnapshotTestCase/Core - FLKAutoLayout (0.1.1) - fmemopen (0.0.1) - - Forgeries (0.1.0) + - Forgeries (1.0.0): + - Forgeries/Core (= 1.0.0) + - Forgeries/Core (1.0.0) - HockeySDK-Source (3.8.1) - ISO8601DateFormatter (0.7) - Keys (1.0.0) - Mixpanel (2.8.3): - Mixpanel/Mixpanel (= 2.8.3) - Mixpanel/Mixpanel (2.8.3) - - Moya/Core (5.3.0): - - Alamofire (~> 3.0) - - Result (~> 1.0) - - Moya/RxSwift (5.3.0): + - Moya/Core (8.0.0-beta.2): + - Alamofire (~> 4.0.0) + - Result + - Moya/RxSwift (8.0.0-beta.2): - Moya/Core - - RxSwift (= 2.0.0) - - Nimble (4.1.0) + - RxSwift + - Nimble (5.0.0) - Nimble-Snapshots (4.1.0): - FBSnapshotTestCase (~> 2.0) - Nimble @@ -58,18 +60,18 @@ PODS: - NJKWebViewProgress/ProgressView (= 0.2.3) - NJKWebViewProgress/Core (0.2.3) - NJKWebViewProgress/ProgressView (0.2.3) - - NSObject+Rx (1.2.0): - - RxSwift (~> 2.0.0) + - NSObject+Rx (2.0.0): + - RxSwift - ORStackView (2.0.0): - FLKAutoLayout (~> 0.1) - - Quick (0.9.3) + - Quick (0.10.0) - Reachability (3.1.1) - - Result (1.0.2) - - RxBlocking (2.0.0): - - RxSwift (~> 2.0) - - RxCocoa (2.0.0): - - RxSwift (~> 2.0) - - RxSwift (2.0.0) + - Result (3.0.0) + - RxBlocking (3.0.0-beta.2): + - RxSwift (~> 3.0.0-beta.2) + - RxCocoa (3.0.0-beta.2): + - RxSwift (~> 3.0.0-beta.2) + - RxSwift (3.0.0-beta.2) - SDWebImage (3.7.1): - SDWebImage/Core (= 3.7.1) - SDWebImage/Core (3.7.1) @@ -77,14 +79,14 @@ PODS: - Stripe/Core (= 3.1.0) - Stripe/Core (3.1.0) - SVProgressHUD (1.1.3) - - SwiftyJSON (2.3.2) + - SwiftyJSON (3.1.0) - UIImageViewAligned (0.0.1) - UIView+BooleanAnimations (1.0.2) - XNGMarkdownParser (0.3.0): - fmemopen DEPENDENCIES: - - Action + - Action (= 2.0.0-beta.1) - ARAnalytics/HockeyApp - ARAnalytics/Mixpanel - ARCollectionViewMasonryLayout (~> 2.0.0) @@ -102,16 +104,16 @@ DEPENDENCIES: - Forgeries - ISO8601DateFormatter (= 0.7) - Keys (from `Pods/CocoaPodsKeys`) - - Moya/RxSwift + - Moya/RxSwift (= 8.0.0-beta.2) - Nimble - Nimble-Snapshots - NSObject+Rx - ORStackView - Quick - Reachability (from `https://github.com/ashfurrow/Reachability.git`, branch `frameworks`) - - RxBlocking - - RxCocoa - - RxSwift + - RxBlocking (= 3.0.0-beta.2) + - RxCocoa (= 3.0.0-beta.2) + - RxSwift (= 3.0.0-beta.2) - SDWebImage (~> 3.7) - Stripe - SVProgressHUD @@ -154,8 +156,8 @@ CHECKOUT OPTIONS: :git: https://github.com/ashfurrow/UIImageViewAligned.git SPEC CHECKSUMS: - Action: 25745dd09111b2a4bdf6164b2338b2104ea5696f - Alamofire: 01a82e2f6c0f860ade35534c8dd88be61bdef40c + Action: 13a8e52481834751caf428bcf6c527e3db2aefc7 + Alamofire: 7682d43245de14874acd142ec137b144aa1dd335 ARAnalytics: 5468652928cee7100dd0e6b5162631409da37106 ARCollectionViewMasonryLayout: 164b82010cf8ec99bc7a38cffe59a179d7e5a116 ARTiledImageView: a747cff42142ca04d1dc5cee516186f10b6c0949 @@ -170,27 +172,27 @@ SPEC CHECKSUMS: FBSnapshotTestCase: 366ecd378511d7716c79991cd8067d1eed23578d FLKAutoLayout: 95b12c5cd9652100235140b68652f063f7437851 fmemopen: dc31f7d3004644b33deee95e047305d23f8cfdd4 - Forgeries: ce03936ada2a8d33d3711d7bb7123be505b83430 + Forgeries: 64ced144ea8341d89a7eec9d1d7986f0f1366250 HockeySDK-Source: d03f7d267d78980af33fb63ddea5199f5bf84049 ISO8601DateFormatter: ab926648eebe497f4d167c0fd083992f959f1274 Keys: 7d2ff6ff42f6903358267ce56e9392f83c31acfe Mixpanel: fd52e097ae0d27295d0595b2261c60b94d191fda - Moya: dfd9310f3dfb198927976dad445ead477ca6c2a3 - Nimble: 97a0a4cae5124c117115634b2d055d8c97d0af19 + Moya: 920d40f738e6f4ece84010cec701bb9693be8251 + Nimble: 56fc9f5020effa2206de22c3dd910f4fb011b92f Nimble-Snapshots: 2da4d2ec829b458d6886c06812c52493fce284c9 NJKWebViewProgress: f481fd424cb5ecc27eacae11b5397db92fba9a4d - NSObject+Rx: 552d0a5bb78087b0faf9f8a066148577f1231426 + NSObject+Rx: 2a9cd801d9c847e6d2486cbad8d7701b67834e70 ORStackView: 5df6b1b990b0648d8ef6f69f89361ea8648e8f64 - Quick: 13a2a2b19a5d8e3ed4fd0c36ee46597fd77ebf71 + Quick: 5d290df1c69d5ee2f0729956dcf0fd9a30447eaa Reachability: 2fc2badcac191cd1a15044bc7fe45aa96c499ece - Result: dd3dd71af3fa2e262f1a999e14fba2c25ec14f16 - RxBlocking: a95a10ed54eb5d116f6c9a87047ff671e181d07b - RxCocoa: b0ebd70b4f7450bdb3c8987b122f8fd568dc1e2f - RxSwift: d83246efa6f16c50c143bec134649d045498f601 + Result: 1b3e431f37cbcd3ad89c6aa9ab0ae55515fae3b6 + RxBlocking: 75dad8ed874b732da94780fdf7cf48eabf0d5d2b + RxCocoa: 1dd105d8a4e18034ccd7bd6d136445363ce67c8e + RxSwift: 0ea939d7d97cb70657190ffde8dc2a9b455778fc SDWebImage: ed3095af2ff88b436426037444979b917f6c5575 Stripe: 8a456332f4db97bd0b8457fc8cce1e1a69ac0cd1 SVProgressHUD: 748080e4f36e603f6c02aec292664239df5279c1 - SwiftyJSON: 04ccea08915aa0109039157c7974cf0298da292a + SwiftyJSON: 29b9f2a64f118c07e691667c2ba1efe536acfe0b UIImageViewAligned: 5726c9651898342c19588a02e2115cace59342ee UIView+BooleanAnimations: a760be9a066036e55f298b7b7350a6cb14cfcd97 XNGMarkdownParser: 562999edfa0e0913dc345f0931bdd78be59f826e From 006d4de13b2e3972b7733f09b9473b8d01a064a9 Mon Sep 17 00:00:00 2001 From: Ash Furrow Date: Tue, 4 Oct 2016 16:07:55 -0400 Subject: [PATCH 02/46] [Swift 3] Initial Xcode migration, modulo edits. --- Kiosk.xcodeproj/project.pbxproj | 6 + .../AdminCardTestingViewController.swift | 10 +- Kiosk/Admin/AdminLogViewController.swift | 14 +- Kiosk/Admin/AdminPanelViewController.swift | 44 +-- Kiosk/Admin/AuctionWebViewController.swift | 8 +- Kiosk/Admin/ChooseAuctionViewController.swift | 6 +- Kiosk/Admin/PasswordAlertViewController.swift | 18 +- Kiosk/App/APIPingManager.swift | 6 +- Kiosk/App/AppDelegate+GlobalActions.swift | 16 +- Kiosk/App/AppDelegate.swift | 32 +- Kiosk/App/AppSetup.swift | 10 +- Kiosk/App/AppViewController.swift | 20 +- Kiosk/App/BidderDetailsRetrieval.swift | 10 +- Kiosk/App/CardHandler.swift | 10 +- Kiosk/App/Constants.swift | 8 +- Kiosk/App/GlobalFunctions.swift | 34 +- Kiosk/App/Logger.swift | 32 +- Kiosk/App/MarkdownParser.swift | 12 +- Kiosk/App/Models/Artist.swift | 2 +- Kiosk/App/Models/Artwork.swift | 18 +- Kiosk/App/Models/Bid.swift | 2 +- Kiosk/App/Models/Bidder.swift | 2 +- Kiosk/App/Models/BidderPosition.swift | 6 +- Kiosk/App/Models/BuyersPremium.swift | 2 +- Kiosk/App/Models/Card.swift | 2 +- Kiosk/App/Models/GenericError.swift | 2 +- Kiosk/App/Models/Image.swift | 12 +- Kiosk/App/Models/Location.swift | 2 +- Kiosk/App/Models/Sale.swift | 12 +- Kiosk/App/Models/SaleArtwork.swift | 14 +- Kiosk/App/Models/SaleArtworkViewModel.swift | 18 +- Kiosk/App/Models/SystemTime.swift | 12 +- Kiosk/App/Models/User.swift | 2 +- Kiosk/App/NSErrorExtensions.swift | 6 +- Kiosk/App/Networking/APIKeys.swift | 2 +- Kiosk/App/Networking/ArtsyAPI.swift | 300 +++++++++--------- Kiosk/App/Networking/NetworkLogger.swift | 6 +- Kiosk/App/Networking/Networking.swift | 20 +- Kiosk/App/Networking/XAppToken.swift | 22 +- Kiosk/App/OfflineViewController.swift | 6 +- Kiosk/App/SwiftExtensions.swift | 10 +- Kiosk/App/UIViewControllerExtensions.swift | 10 +- .../App/UIViewSubclassesErrorExtensions.swift | 22 +- .../App/Views/Button Subclasses/Button.swift | 64 ++-- Kiosk/App/Views/RegisterFlowView.swift | 46 +-- Kiosk/App/Views/SimulatorOnlyView.swift | 6 +- Kiosk/App/Views/Spinner.swift | 20 +- Kiosk/App/Views/SwitchView.swift | 92 +++--- Kiosk/App/Views/Text Fields/CursorView.swift | 8 +- Kiosk/App/Views/Text Fields/TextField.swift | 54 ++-- .../ListingsCountdownManager.swift | 24 +- .../ListingsViewController.swift | 58 ++-- .../Auction Listings/ListingsViewModel.swift | 132 ++++---- .../MasonryCollectionViewCell.swift | 40 +-- .../TableCollectionViewCell.swift | 52 +-- .../Auction Listings/WebViewController.swift | 28 +- .../AdminCCBypassNetworkModel.swift | 8 +- .../BidCheckingNetworkModel.swift | 24 +- .../BidDetailsPreviewView.swift | 20 +- .../Bid Fulfillment/BidderNetworkModel.swift | 6 +- ...nfirmYourBidArtsyLoginViewController.swift | 48 +-- ...mYourBidEnterYourEmailViewController.swift | 18 +- .../ConfirmYourBidPINViewController.swift | 29 +- ...ConfirmYourBidPasswordViewController.swift | 4 +- .../ConfirmYourBidViewController.swift | 44 +-- .../FulfillmentContainerViewController.swift | 14 +- .../FulfillmentNavigationController.swift | 6 +- .../Bid Fulfillment/KeypadContainerView.swift | 10 +- Kiosk/Bid Fulfillment/KeypadView.swift | 8 +- Kiosk/Bid Fulfillment/KeypadViewModel.swift | 2 +- .../LoadingViewController.swift | 52 +-- .../ManualCreditCardInputViewController.swift | 102 +++--- .../ManualCreditCardInputViewModel.swift | 12 +- .../Models/RegistrationCoordinator.swift | 68 ++-- .../PlaceBidNetworkModel.swift | 8 +- .../PlaceBidViewController.swift | 78 ++--- .../RegisterViewController.swift | 10 +- .../RegistrationEmailViewController.swift | 8 +- .../RegistrationMobileViewController.swift | 12 +- .../RegistrationPasswordViewController.swift | 8 +- .../RegistrationPasswordViewModel.swift | 4 +- .../RegistrationPostalZipViewController.swift | 6 +- Kiosk/Bid Fulfillment/StripeManager.swift | 16 +- .../SwipeCreditCardViewController.swift | 20 +- .../YourBiddingDetailsViewController.swift | 4 +- Kiosk/Help/HelpAnimator.swift | 34 +- Kiosk/Help/HelpViewController.swift | 68 ++-- Kiosk/HelperFunctions.swift | 16 +- Kiosk/ListingsCollectionViewCell.swift | 36 +-- Kiosk/Observable+JSONAble.swift | 6 +- Kiosk/Observable+Logging.swift | 2 +- Kiosk/Observable+Operators.swift | 21 +- .../ImageTiledDataSource.swift | 4 +- .../SaleArtworkDetailsViewController.swift | 150 ++++----- .../SaleArtworkZoomViewController.swift | 14 +- .../WhitespaceGobbler.swift | 8 +- .../Storyboards/UIStoryboardExtensions.swift | 4 +- Kiosk/UILabel+Fonts.swift | 14 +- Kiosk/UIView+LongPressDisplayMessage.swift | 12 +- Kiosk/UIViewController+Bidding.swift | 4 +- KioskTests/App/SwiftExtensionsTests.swift | 2 +- KioskTests/ArtsyAPISpec.swift | 2 +- .../AdminCCBypassNetworkModelTests.swift | 2 +- .../PlaceBidViewControllerTests.swift | 10 +- ...istrationPasswordViewControllerTests.swift | 2 +- .../RegistrationPasswordViewModelTests.swift | 4 +- .../Bid Fulfillment/StripeManagerTests.swift | 2 +- KioskTests/HelpViewControllerTests.swift | 4 +- KioskTests/ListingsViewControllerTests.swift | 16 +- KioskTests/ListingsViewModelTests.swift | 6 +- KioskTests/Models/ArtworkTests.swift | 2 +- KioskTests/Models/ImageTests.swift | 2 +- KioskTests/Models/SaleTests.swift | 6 +- KioskTests/RegisterFlowViewTests.swift | 2 +- ...aleArtworkDetailsViewControllerTests.swift | 4 +- KioskTests/TestHelpers.swift | 40 +-- 116 files changed, 1292 insertions(+), 1288 deletions(-) diff --git a/Kiosk.xcodeproj/project.pbxproj b/Kiosk.xcodeproj/project.pbxproj index c48df126..c70fdfc8 100644 --- a/Kiosk.xcodeproj/project.pbxproj +++ b/Kiosk.xcodeproj/project.pbxproj @@ -953,9 +953,11 @@ 5EB0B6EC1A5C3E6800B7CBF2 = { CreatedOnToolsVersion = 6.1.1; DevelopmentTeam = ZH8TGFYKDK; + LastSwiftMigration = 0800; }; 5EB0B7011A5C3E6800B7CBF2 = { CreatedOnToolsVersion = 6.1.1; + LastSwiftMigration = 0800; TestTargetID = 5EB0B6EC1A5C3E6800B7CBF2; }; }; @@ -1432,6 +1434,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Kiosk/Supporting Files/BridgingHeader.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; @@ -1459,6 +1462,7 @@ PROVISIONING_PROFILE = "$(PROFILE_UDID)"; SWIFT_OBJC_BRIDGING_HEADER = "Kiosk/Supporting Files/BridgingHeader.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; @@ -1484,6 +1488,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "net.artsy.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Kiosk.app/Kiosk"; }; name = Debug; @@ -1505,6 +1510,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "net.artsy.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Kiosk.app/Kiosk"; }; name = Release; diff --git a/Kiosk/Admin/AdminCardTestingViewController.swift b/Kiosk/Admin/AdminCardTestingViewController.swift index 99f64db6..79137033 100644 --- a/Kiosk/Admin/AdminCardTestingViewController.swift +++ b/Kiosk/Admin/AdminCardTestingViewController.swift @@ -48,19 +48,19 @@ class AdminCardTestingViewController: UIViewController { cardHandler.startSearching() } - override func viewWillDisappear(animated: Bool) { + override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) cardHandler.end() } - func log(string: String) { + func log(_ string: String) { self.logTextView.text = "\(self.logTextView.text)\n\(string)" } - @IBAction func backTapped(sender: AnyObject) { - navigationController?.popViewControllerAnimated(true) + @IBAction func backTapped(_ sender: AnyObject) { + navigationController?.popViewController(animated: true) } -} \ No newline at end of file +} diff --git a/Kiosk/Admin/AdminLogViewController.swift b/Kiosk/Admin/AdminLogViewController.swift index 46c8f92f..053c317f 100644 --- a/Kiosk/Admin/AdminLogViewController.swift +++ b/Kiosk/Admin/AdminLogViewController.swift @@ -4,20 +4,20 @@ class AdminLogViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - textView.text = try? NSString(contentsOfURL: logPath(), encoding: NSASCIIStringEncoding) as String + textView.text = try? NSString(contentsOf: logPath(), encoding: String.Encoding.ascii.rawValue) as String } @IBOutlet weak var textView: UITextView! - @IBAction func backButtonTapped(sender: AnyObject) { - self.navigationController?.popViewControllerAnimated(true) + @IBAction func backButtonTapped(_ sender: AnyObject) { + self.navigationController?.popViewController(animated: true) } - func logPath() -> NSURL { - let docs = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).last! - return docs.URLByAppendingPathComponent("logger.txt") + func logPath() -> URL { + let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last! + return docs.appendingPathComponent("logger.txt") } - @IBAction func scrollTapped(sender: AnyObject) { + @IBAction func scrollTapped(_ sender: AnyObject) { textView.scrollRangeToVisible(NSMakeRange(textView.text.characters.count - 1, 1)) } } diff --git a/Kiosk/Admin/AdminPanelViewController.swift b/Kiosk/Admin/AdminPanelViewController.swift index 0767704b..0c7e5c20 100644 --- a/Kiosk/Admin/AdminPanelViewController.swift +++ b/Kiosk/Admin/AdminPanelViewController.swift @@ -6,28 +6,28 @@ class AdminPanelViewController: UIViewController { @IBOutlet weak var auctionIDLabel: UILabel! - @IBAction func backTapped(sender: AnyObject) { - self.presentingViewController?.dismissViewControllerAnimated(true, completion: nil) + @IBAction func backTapped(_ sender: AnyObject) { + self.presentingViewController?.dismiss(animated: true, completion: nil) appDelegate().setHelpButtonHidden(false) } - @IBAction func closeAppTapped(sender: AnyObject) { + @IBAction func closeAppTapped(_ sender: AnyObject) { exit(1) } - override func viewDidAppear(animated: Bool) { + override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) appDelegate().setHelpButtonHidden(true) } - override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue == .LoadAdminWebViewController { - let webVC = segue.destinationViewController as! AuctionWebViewController + let webVC = segue.destination as! AuctionWebViewController let auctionID = AppSetup.sharedState.auctionID let base = AppSetup.sharedState.useStaging ? "staging.artsy.net" : "artsy.net" - webVC.URL = NSURL(string: "https://\(base)/feature/\(auctionID)")! + webVC.url = URL(string: "https://\(base)/feature/\(auctionID)")! // TODO: Hide help button } @@ -40,27 +40,27 @@ class AdminPanelViewController: UIViewController { if APIKeys.sharedKeys.stubResponses { auctionIDLabel.text = "STUBBING API RESPONSES\nNOT CONTACTING ARTSY API" } else { - let version = (NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleShortVersionString") as? String) ?? "Unknown" + let version = (Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String) ?? "Unknown" auctionIDLabel.text = "\(state.auctionID), Kiosk version: \(version)" } let environment = state.useStaging ? "PRODUCTION" : "STAGING" - environmentChangeButton.setTitle("USE \(environment)", forState: .Normal) + environmentChangeButton.setTitle("USE \(environment)", for: UIControlState()) let buttonsTitle = state.showDebugButtons ? "HIDE" : "SHOW" - showAdminButtonsButton.setTitle(buttonsTitle, forState: .Normal) + showAdminButtonsButton.setTitle(buttonsTitle, for: UIControlState()) let readStatus = state.disableCardReader ? "ENABLE" : "DISABLE" - toggleCardReaderButton.setTitle(readStatus, forState: .Normal) + toggleCardReaderButton.setTitle(readStatus, for: UIControlState()) } @IBOutlet weak var environmentChangeButton: UIButton! - @IBAction func switchStagingProductionTapped(sender: AnyObject) { - let defaults = NSUserDefaults.standardUserDefaults() - defaults.setObject(!AppSetup.sharedState.useStaging, forKey: "KioskUseStaging") + @IBAction func switchStagingProductionTapped(_ sender: AnyObject) { + let defaults = UserDefaults.standard + defaults.set(!AppSetup.sharedState.useStaging, forKey: "KioskUseStaging") - defaults.removeObjectForKey(XAppToken.DefaultsKeys.TokenKey.rawValue) - defaults.removeObjectForKey(XAppToken.DefaultsKeys.TokenExpiry.rawValue) + defaults.removeObject(forKey: XAppToken.DefaultsKeys.TokenKey.rawValue) + defaults.removeObject(forKey: XAppToken.DefaultsKeys.TokenExpiry.rawValue) defaults.synchronize() delayToMainThread(1){ @@ -70,9 +70,9 @@ class AdminPanelViewController: UIViewController { } @IBOutlet weak var showAdminButtonsButton: UIButton! - @IBAction func toggleAdminButtons(sender: UIButton) { - let defaults = NSUserDefaults.standardUserDefaults() - defaults.setObject(!AppSetup.sharedState.showDebugButtons, forKey: "KioskShowDebugButtons") + @IBAction func toggleAdminButtons(_ sender: UIButton) { + let defaults = UserDefaults.standard + defaults.set(!AppSetup.sharedState.showDebugButtons, forKey: "KioskShowDebugButtons") defaults.synchronize() delayToMainThread(1){ exit(1) @@ -82,9 +82,9 @@ class AdminPanelViewController: UIViewController { @IBOutlet weak var cardReaderLabel: ARSerifLabel! @IBOutlet weak var toggleCardReaderButton: SecondaryActionButton! - @IBAction func toggleCardReaderTapped(sender: AnyObject) { - let defaults = NSUserDefaults.standardUserDefaults() - defaults.setObject(!AppSetup.sharedState.disableCardReader, forKey: "KioskDisableCardReader") + @IBAction func toggleCardReaderTapped(_ sender: AnyObject) { + let defaults = UserDefaults.standard + defaults.set(!AppSetup.sharedState.disableCardReader, forKey: "KioskDisableCardReader") defaults.synchronize() delayToMainThread(1){ exit(1) diff --git a/Kiosk/Admin/AuctionWebViewController.swift b/Kiosk/Admin/AuctionWebViewController.swift index f0434a90..d33bd348 100644 --- a/Kiosk/Admin/AuctionWebViewController.swift +++ b/Kiosk/Admin/AuctionWebViewController.swift @@ -5,19 +5,19 @@ class AuctionWebViewController: WebViewController { override func viewDidLoad() { super.viewDidLoad() - let flexibleSpace = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FlexibleSpace, target: nil, action: nil) + let flexibleSpace = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.flexibleSpace, target: nil, action: nil) let exitImage = UIImage(named: "toolbar_close") - let backwardBarItem = UIBarButtonItem(image: exitImage, style: .Plain, target: self, action: #selector(exit)); + let backwardBarItem = UIBarButtonItem(image: exitImage, style: .plain, target: self, action: #selector(exit)); let allItems = self.toolbarItems! + [flexibleSpace, backwardBarItem] toolbarItems = allItems } func exit() { let passwordVC = PasswordAlertViewController.alertView { [weak self] in - self?.navigationController?.popViewControllerAnimated(true) + self?.navigationController?.popViewController(animated: true) return } - self.presentViewController(passwordVC, animated: true) {} + self.present(passwordVC, animated: true) {} } } diff --git a/Kiosk/Admin/ChooseAuctionViewController.swift b/Kiosk/Admin/ChooseAuctionViewController.swift index 19383d08..d86c2023 100644 --- a/Kiosk/Admin/ChooseAuctionViewController.swift +++ b/Kiosk/Admin/ChooseAuctionViewController.swift @@ -14,7 +14,7 @@ class ChooseAuctionViewController: UIViewController { stackScrollView.bottomMarginHeight = CGFloat(NSNotFound) stackScrollView.updateConstraints() - let endpoint: ArtsyAPI = ArtsyAPI.ActiveAuctions + let endpoint: ArtsyAPI = ArtsyAPI.activeAuctions provider.request(endpoint) .filterSuccessfulStatusCodes() @@ -48,7 +48,7 @@ class ChooseAuctionViewController: UIViewController { } @IBOutlet weak var stackScrollView: ORStackView! - @IBAction func backButtonTapped(sender: AnyObject) { - self.navigationController?.popViewControllerAnimated(true) + @IBAction func backButtonTapped(_ sender: AnyObject) { + self.navigationController?.popViewController(animated: true) } } diff --git a/Kiosk/Admin/PasswordAlertViewController.swift b/Kiosk/Admin/PasswordAlertViewController.swift index 6b2edd2c..fd78a9a1 100644 --- a/Kiosk/Admin/PasswordAlertViewController.swift +++ b/Kiosk/Admin/PasswordAlertViewController.swift @@ -3,27 +3,27 @@ import UIKit class PasswordAlertViewController: UIAlertController { - class func alertView(completion: () -> ()) -> PasswordAlertViewController { - let alertController = PasswordAlertViewController(title: "Exit Kiosk", message: nil, preferredStyle: .Alert) - let exitAction = UIAlertAction(title: "Exit", style: .Default) { (_) in + class func alertView(completion: @escaping () -> ()) -> PasswordAlertViewController { + let alertController = PasswordAlertViewController(title: "Exit Kiosk", message: nil, preferredStyle: .alert) + let exitAction = UIAlertAction(title: "Exit", style: .default) { (_) in completion() return } if detectDevelopmentEnvironment() { - exitAction.enabled = true + exitAction.isEnabled = true } else { - exitAction.enabled = false + exitAction.isEnabled = false } - let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel) { (_) in } + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (_) in } - alertController.addTextFieldWithConfigurationHandler { (textField) in + alertController.addTextField { (textField) in textField.placeholder = "Exit Password" - NSNotificationCenter.defaultCenter().addObserverForName(UITextFieldTextDidChangeNotification, object: textField, queue: NSOperationQueue.mainQueue()) { (notification) in + NotificationCenter.default.addObserver(forName: NSNotification.Name.UITextFieldTextDidChange, object: textField, queue: OperationQueue.main) { (notification) in // compiler crashes when using weak - exitAction.enabled = textField.text == "Genome401" + exitAction.isEnabled = textField.text == "Genome401" } } diff --git a/Kiosk/App/APIPingManager.swift b/Kiosk/App/APIPingManager.swift index d135d0c2..b2faba30 100644 --- a/Kiosk/App/APIPingManager.swift +++ b/Kiosk/App/APIPingManager.swift @@ -4,7 +4,7 @@ import RxSwift class APIPingManager { - let syncInterval: NSTimeInterval = 2 + let syncInterval: TimeInterval = 2 var letOnline: Observable! var provider: Networking @@ -18,7 +18,7 @@ class APIPingManager { .startWith(true) } - private func ping() -> Observable { + fileprivate func ping() -> Observable { return provider.request(ArtsyAPI.Ping).map(responseIsOK) } -} \ No newline at end of file +} diff --git a/Kiosk/App/AppDelegate+GlobalActions.swift b/Kiosk/App/AppDelegate+GlobalActions.swift index 0ff890c1..db8fb296 100644 --- a/Kiosk/App/AppDelegate+GlobalActions.swift +++ b/Kiosk/App/AppDelegate+GlobalActions.swift @@ -5,7 +5,7 @@ import RxSwift import Action func appDelegate() -> AppDelegate { - return UIApplication.sharedApplication().delegate as! AppDelegate + return UIApplication.shared.delegate as! AppDelegate } extension AppDelegate { @@ -25,10 +25,10 @@ extension AppDelegate { func setupHelpButton() { helpButton = MenuButton() - helpButton.setTitle("Help", forState: .Normal) + helpButton.setTitle("Help", for: .normal) helpButton.rx_action = helpButtonCommand() window?.addSubview(helpButton) - helpButton.alignTop(nil, leading: nil, bottom: "-24", trailing: "-24", toView: window) + helpButton.alignTop(nil, leading: nil, bottom: "-24", trailing: "-24", to: window) window?.layoutIfNeeded() helpIsVisisble.subscribeNext { visisble in @@ -47,8 +47,8 @@ extension AppDelegate { }.addDisposableTo(rx_disposeBag) } - func setHelpButtonHidden(hidden: Bool) { - helpButton.hidden = hidden + func setHelpButtonHidden(_ hidden: Bool) { + helpButton.isHidden = hidden } } @@ -122,7 +122,7 @@ private extension AppDelegate { func showBidderDetailsRetrieval() -> Observable { let appVC = self.appViewController - let presentingViewController: UIViewController = (appVC.presentedViewController ?? appVC) + let presentingViewController: UIViewController = (appVC!.presentedViewController ?? appVC!) return presentingViewController.promptForBidderDetailsRetrieval(self.provider) } @@ -260,11 +260,11 @@ private extension AppDelegate { // MARK: - Help transtion animation extension AppDelegate: UIViewControllerTransitioningDelegate { - func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? { + func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { return HelpAnimator(presenting: true) } - func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { + func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return HelpAnimator() } } diff --git a/Kiosk/App/AppDelegate.swift b/Kiosk/App/AppDelegate.swift index 3c98e8a6..0ee559d8 100644 --- a/Kiosk/App/AppDelegate.swift +++ b/Kiosk/App/AppDelegate.swift @@ -13,14 +13,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate { weak var webViewController: UIViewController? - var window: UIWindow? = UIWindow(frame:CGRectMake(0, 0, UIScreen.mainScreen().bounds.height, UIScreen.mainScreen().bounds.width)) + var window: UIWindow? = UIWindow(frame:CGRect(x: 0, y: 0, width: UIScreen.main.bounds.height, height: UIScreen.main.bounds.width)) - private(set) var provider = Networking.newDefaultNetworking() + fileprivate(set) var provider = Networking.newDefaultNetworking() - func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // Disable sleep timer - UIApplication.sharedApplication().idleTimerDisabled = true + UIApplication.shared.isIdleTimerDisabled = true // Set up network layer if StubResponses.stubResponses() { @@ -32,12 +32,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate { if let _ = NSClassFromString("XCTest") { return true } // Clear possible old contents from cache and defaults. - let imageCache = SDImageCache.sharedImageCache() - imageCache.clearDisk() + let imageCache = SDImageCache.shared() + imageCache?.clearDisk() - let defaults = NSUserDefaults.standardUserDefaults() - defaults.removeObjectForKey(XAppToken.DefaultsKeys.TokenKey.rawValue) - defaults.removeObjectForKey(XAppToken.DefaultsKeys.TokenExpiry.rawValue) + let defaults = UserDefaults.standard + defaults.removeObject(forKey: XAppToken.DefaultsKeys.TokenKey.rawValue) + defaults.removeObject(forKey: XAppToken.DefaultsKeys.TokenExpiry.rawValue) let auctionStoryboard = UIStoryboard.auction() let appViewController = auctionStoryboard.instantiateInitialViewController() as? AppViewController @@ -55,7 +55,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { let mixpanelToken = AppSetup.sharedState.useStaging ? keys.mixpanelStagingAPIClientKey() : keys.mixpanelProductionAPIClientKey() - ARAnalytics.setupWithAnalytics([ + ARAnalytics.setup(withAnalytics: [ ARHockeyAppBetaID: keys.hockeyBetaSecret(), ARHockeyAppLiveID: keys.hockeyProductionSecret(), ARMixpanelToken: mixpanelToken @@ -70,17 +70,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } func setupUserAgent() { - let version = NSBundle.mainBundle().infoDictionary?["CFBundleShortVersionString"] as! String? - let build = NSBundle.mainBundle().infoDictionary?["CFBundleVersion"] as! String? + let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as! String? + let build = Bundle.main.infoDictionary?["CFBundleVersion"] as! String? - let webView = UIWebView(frame: CGRectZero) - let oldAgent = webView.stringByEvaluatingJavaScriptFromString("navigator.userAgent") + let webView = UIWebView(frame: CGRect.zero) + let oldAgent = webView.stringByEvaluatingJavaScript(from: "navigator.userAgent") let agentString = "\(oldAgent) Artsy-Mobile/\(version!) Eigen/\(build!) Kiosk Eidolon" - let defaults = NSUserDefaults.standardUserDefaults() + let defaults = UserDefaults.standard let userAgentDict = ["UserAgent": agentString] - defaults.registerDefaults(userAgentDict) + defaults.register(defaults: userAgentDict) } } diff --git a/Kiosk/App/AppSetup.swift b/Kiosk/App/AppSetup.swift index 4b80d1ce..fce09c47 100644 --- a/Kiosk/App/AppSetup.swift +++ b/Kiosk/App/AppSetup.swift @@ -16,14 +16,14 @@ class AppSetup { } init() { - let defaults = NSUserDefaults.standardUserDefaults() - if let auction = defaults.stringForKey("KioskAuctionID") { + let defaults = UserDefaults.standard + if let auction = defaults.string(forKey: "KioskAuctionID") { auctionID = auction } - useStaging = defaults.boolForKey("KioskUseStaging") - showDebugButtons = defaults.boolForKey("KioskShowDebugButtons") - disableCardReader = defaults.boolForKey("KioskDisableCardReader") + useStaging = defaults.bool(forKey: "KioskUseStaging") + showDebugButtons = defaults.bool(forKey: "KioskShowDebugButtons") + disableCardReader = defaults.bool(forKey: "KioskDisableCardReader") if let _ = NSClassFromString("XCTest") { isTesting = true } } diff --git a/Kiosk/App/AppViewController.swift b/Kiosk/App/AppViewController.swift index 410042d6..bd0b4479 100644 --- a/Kiosk/App/AppViewController.swift +++ b/Kiosk/App/AppViewController.swift @@ -28,11 +28,11 @@ class AppViewController: UIViewController, UINavigationControllerDelegate { appDelegate().registerToBidCommand() } - class func instantiateFromStoryboard(storyboard: UIStoryboard) -> AppViewController { + class func instantiate(from storyboard: UIStoryboard) -> AppViewController { return storyboard.viewControllerWithID(.AppViewController) as! AppViewController } - var sale = Variable(Sale(id: "", name: "", isAuction: true, startDate: NSDate(), endDate: NSDate(), artworkCount: 0, state: "")) + var sale = Variable(Sale(id: "", name: "", isAuction: true, startDate: Date(), endDate: Date(), artworkCount: 0, state: "")) override func viewDidLoad() { super.viewDidLoad() @@ -63,9 +63,9 @@ class AppViewController: UIViewController, UINavigationControllerDelegate { } } - override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { // This is the embed segue - guard let navigtionController = segue.destinationViewController as? UINavigationController else { return } + guard let navigtionController = segue.destination as? UINavigationController else { return } guard let listingsViewController = navigtionController.topViewController as? ListingsViewController else { return } listingsViewController.provider = provider @@ -75,17 +75,17 @@ class AppViewController: UIViewController, UINavigationControllerDelegate { countdownManager.invalidate() } - func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) { + func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) { let hide = (viewController as? SaleArtworkZoomViewController != nil) countdownManager.setLabelsHiddenIfSynced(hide) - registerToBidButton.hidden = hide + registerToBidButton.isHidden = hide } } extension AppViewController { - @IBAction func longPressForAdmin(sender: UIGestureRecognizer) { - if sender.state != .Began { + @IBAction func longPressForAdmin(_ sender: UIGestureRecognizer) { + if sender.state != .began { return } @@ -93,10 +93,10 @@ extension AppViewController { self?.performSegue(.ShowAdminOptions) return } - self.presentViewController(passwordVC, animated: true) {} + self.present(passwordVC, animated: true) {} } - func auctionRequest(provider: Networking, auctionID: String) -> Observable { + func auctionRequest(_ provider: Networking, auctionID: String) -> Observable { let auctionEndpoint: ArtsyAPI = ArtsyAPI.AuctionInfo(auctionID: auctionID) return provider.request(auctionEndpoint) diff --git a/Kiosk/App/BidderDetailsRetrieval.swift b/Kiosk/App/BidderDetailsRetrieval.swift index 94df3540..26504e2b 100644 --- a/Kiosk/App/BidderDetailsRetrieval.swift +++ b/Kiosk/App/BidderDetailsRetrieval.swift @@ -37,7 +37,7 @@ extension UIViewController { } func emailPromptAlertController(provider: Networking) -> UIAlertController { - let alertController = UIAlertController(title: "Send Bidder Details", message: "Enter your email address or phone number registered with Artsy and we will send your bidder number and PIN.", preferredStyle: .Alert) + let alertController = UIAlertController(title: "Send Bidder Details", message: "Enter your email address or phone number registered with Artsy and we will send your bidder number and PIN.", preferredStyle: .alert) let ok = UIAlertAction.Action("OK", style: .Default) let action = CocoaAction { _ -> Observable in @@ -48,7 +48,7 @@ extension UIViewController { ok.rx_action = action let cancel = UIAlertAction.Action("Cancel", style: .Cancel) - alertController.addTextFieldWithConfigurationHandler(nil) + alertController.addTextField(configurationHandler: nil) alertController.addAction(ok) alertController.addAction(cancel) @@ -58,14 +58,14 @@ extension UIViewController { extension UIAlertController { class func successfulBidderDetailsAlertController() -> UIAlertController { - let alertController = self.init(title: "Your details have been sent", message: nil, preferredStyle: .Alert) + let alertController = self.init(title: "Your details have been sent", message: nil, preferredStyle: .alert) alertController.addAction(UIAlertAction.Action("OK", style: .Default)) return alertController } class func failedBidderDetailsAlertController() -> UIAlertController { - let alertController = self.init(title: "Incorrect Email", message: "Email was not recognized. You may not be registered to bid yet.", preferredStyle: .Alert) + let alertController = self.init(title: "Incorrect Email", message: "Email was not recognized. You may not be registered to bid yet.", preferredStyle: .alert) alertController.addAction(UIAlertAction.Action("Cancel", style: .Cancel)) let retryAction = UIAlertAction.Action("Retry", style: .Default) @@ -75,4 +75,4 @@ extension UIAlertController { return alertController } -} \ No newline at end of file +} diff --git a/Kiosk/App/CardHandler.swift b/Kiosk/App/CardHandler.swift index de408d5e..b70be259 100644 --- a/Kiosk/App/CardHandler.swift +++ b/Kiosk/App/CardHandler.swift @@ -3,7 +3,7 @@ import RxSwift class CardHandler: NSObject, CFTReaderDelegate { - private let _cardStatus = PublishSubject() + fileprivate let _cardStatus = PublishSubject() var cardStatus: Observable { return _cardStatus.asObservable() @@ -40,7 +40,7 @@ class CardHandler: NSObject, CFTReaderDelegate { reader = nil } - func readerCardResponse(card: CFTCard?, withError error: NSError?) { + func readerCardResponse(_ card: CFTCard?, withError error: NSError?) { if let card = card { self.card = card; _cardStatus.onNext("Got Card") @@ -63,7 +63,7 @@ class CardHandler: NSObject, CFTReaderDelegate { } } - func transactionResult(charge: CFTCharge!, withError error: NSError!) { + func transactionResult(_ charge: CFTCharge!, withError error: NSError!) { logger.log("Unexcepted call to transactionResult callback: \(charge)\n\(error)") } @@ -87,12 +87,12 @@ class CardHandler: NSObject, CFTReaderDelegate { logger.log("Card Reader was Cancelled") } - func readerGenericResponse(cardData: String!) { + func readerGenericResponse(_ cardData: String!) { _cardStatus.onNext("Reader received non-card data: \(cardData) "); reader.beginSwipe() } - func readerIsConnected(isConnected: Bool, withError error: NSError!) { + func readerIsConnected(_ isConnected: Bool, withError error: NSError!) { if isConnected { _cardStatus.onNext("Reader is connected") reader.beginSwipe() diff --git a/Kiosk/App/Constants.swift b/Kiosk/App/Constants.swift index a59d9566..70ba12ae 100644 --- a/Kiosk/App/Constants.swift +++ b/Kiosk/App/Constants.swift @@ -1,9 +1,9 @@ import Foundation struct AnimationDuration { - static let Normal: NSTimeInterval = 0.30 - static let Short: NSTimeInterval = 0.15 + static let Normal: TimeInterval = 0.30 + static let Short: TimeInterval = 0.15 } -let SyncInterval: NSTimeInterval = 60 -let ButtonHeight: CGFloat = 50 \ No newline at end of file +let SyncInterval: TimeInterval = 60 +let ButtonHeight: CGFloat = 50 diff --git a/Kiosk/App/GlobalFunctions.swift b/Kiosk/App/GlobalFunctions.swift index 207b480e..e610e6bc 100644 --- a/Kiosk/App/GlobalFunctions.swift +++ b/Kiosk/App/GlobalFunctions.swift @@ -3,18 +3,14 @@ import Reachability import Moya // Ideally a Pod. For now a file. -func delayToMainThread(delay:Double, closure:()->()) { - dispatch_after ( - dispatch_time( - DISPATCH_TIME_NOW, - Int64(delay * Double(NSEC_PER_SEC)) - ), - dispatch_get_main_queue(), closure) +func delayToMainThread(_ delay:Double, closure:@escaping ()->()) { + DispatchQueue.main.asyncAfter ( + deadline: DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: closure) } -func logPath() -> NSURL { - let docs = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).last! - return docs.URLByAppendingPathComponent("logger.txt") +func logPath() -> URL { + let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last! + return docs.appendingPathComponent("logger.txt") } let logger = Logger(destination: logPath()) @@ -29,7 +25,7 @@ func connectedToInternetOrStubbing() -> Observable { return [online, stubbing].combineLatestOr() } -func responseIsOK(response: Response) -> Bool { +func responseIsOK(_ response: Response) -> Bool { return response.statusCode == 200 } @@ -48,29 +44,29 @@ private class ReachabilityManager: NSObject { return _reach.asObservable() } - private let reachability = Reachability.reachabilityForInternetConnection() + fileprivate let reachability = Reachability.forInternetConnection() override init() { super.init() - reachability.reachableBlock = { [weak self] _ in - dispatch_async(dispatch_get_main_queue()) { + reachability?.reachableBlock = { [weak self] _ in + DispatchQueue.main.async { self?._reach.onNext(true) } } - reachability.unreachableBlock = { [weak self] _ in - dispatch_async(dispatch_get_main_queue()) { + reachability?.unreachableBlock = { [weak self] _ in + DispatchQueue.main.async { self?._reach.onNext(false) } } - reachability.startNotifier() + reachability?.startNotifier() _reach.onNext(reachability.isReachable()) } } -func bindingErrorToInterface(error: ErrorType) { +func bindingErrorToInterface(_ error: Error) { let error = "Binding error to UI: \(error)" #if DEBUG fatalError(error) @@ -80,7 +76,7 @@ func bindingErrorToInterface(error: ErrorType) { } // Applies an instance method to the instance with an unowned reference. -func applyUnowned(instance: Type, _ function: (Type -> Parameters -> ReturnValue)) -> (Parameters -> ReturnValue) { +func applyUnowned(_ instance: Type, _ function: @escaping ((Type) -> (Parameters) -> ReturnValue)) -> ((Parameters) -> ReturnValue) { return { [unowned instance] parameters -> ReturnValue in return function(instance)(parameters) } diff --git a/Kiosk/App/Logger.swift b/Kiosk/App/Logger.swift index 41ceef5b..d6a1cd7f 100644 --- a/Kiosk/App/Logger.swift +++ b/Kiosk/App/Logger.swift @@ -1,20 +1,20 @@ import Foundation class Logger { - let destination: NSURL - lazy private var dateFormatter: NSDateFormatter = { - let formatter = NSDateFormatter() - formatter.locale = NSLocale.currentLocale() + let destination: URL + lazy fileprivate var dateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.locale = Locale.current formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS" return formatter }() - lazy private var fileHandle: NSFileHandle? = { + lazy fileprivate var fileHandle: FileHandle? = { if let path = self.destination.path { - NSFileManager.defaultManager().createFileAtPath(path, contents: nil, attributes: nil) + FileManager.default.createFile(atPath: path, contents: nil, attributes: nil) do { - let fileHandle = try NSFileHandle(forWritingToURL: self.destination) + let fileHandle = try FileHandle(forWritingTo: self.destination) print("Successfully logging to: \(path)") return fileHandle } catch let error as NSError { @@ -28,7 +28,7 @@ class Logger { return nil }() - init(destination: NSURL) { + init(destination: URL) { self.destination = destination } @@ -36,7 +36,7 @@ class Logger { fileHandle?.closeFile() } - func log(message: String, function: String = #function, file: String = #file, line: Int = #line) { + func log(_ message: String, function: String = #function, file: String = #file, line: Int = #line) { let logMessage = stringRepresentation(message, function: function, file: file, line: line) printToConsole(logMessage) @@ -45,20 +45,20 @@ class Logger { } private extension Logger { - func stringRepresentation(message: String, function: String, file: String, line: Int) -> String { - let dateString = dateFormatter.stringFromDate(NSDate()) + func stringRepresentation(_ message: String, function: String, file: String, line: Int) -> String { + let dateString = dateFormatter.string(from: Date()) - let file = NSURL(fileURLWithPath: file).lastPathComponent ?? "(Unknown File)" + let file = URL(fileURLWithPath: file).lastPathComponent ?? "(Unknown File)" return "\(dateString) [\(file):\(line)] \(function): \(message)\n" } - func printToConsole(logMessage: String) { + func printToConsole(_ logMessage: String) { print(logMessage) } - func printToDestination(logMessage: String) { - if let data = logMessage.dataUsingEncoding(NSUTF8StringEncoding) { - fileHandle?.writeData(data) + func printToDestination(_ logMessage: String) { + if let data = logMessage.data(using: String.Encoding.utf8) { + fileHandle?.write(data) } else { print("Serious error in logging: could not encode logged string into data.") } diff --git a/Kiosk/App/MarkdownParser.swift b/Kiosk/App/MarkdownParser.swift index c2c71225..a69288b8 100644 --- a/Kiosk/App/MarkdownParser.swift +++ b/Kiosk/App/MarkdownParser.swift @@ -7,10 +7,10 @@ class MarkdownParser: XNGMarkdownParser { override init() { super.init() - paragraphFont = UIFont.serifFontWithSize(16) - linkFontName = UIFont.serifItalicFontWithSize(16).fontName - boldFontName = UIFont.serifBoldFontWithSize(16).fontName - italicFontName = UIFont.serifItalicFontWithSize(16).fontName + paragraphFont = UIFont.serifFont(withSize: 16) + linkFontName = UIFont.serifItalicFont(withSize: 16).fontName + boldFontName = UIFont.serifBoldFont(withSize: 16).fontName + italicFontName = UIFont.serifItalicFont(withSize: 16).fontName shouldParseLinks = false let paragraphStyle = NSMutableParagraphStyle() @@ -18,7 +18,7 @@ class MarkdownParser: XNGMarkdownParser { topAttributes = [ NSParagraphStyleAttributeName: paragraphStyle, - NSForegroundColorAttributeName: UIColor.blackColor() + NSForegroundColorAttributeName: UIColor.black ] } -} \ No newline at end of file +} diff --git a/Kiosk/App/Models/Artist.swift b/Kiosk/App/Models/Artist.swift index 023573fc..328c5bbb 100644 --- a/Kiosk/App/Models/Artist.swift +++ b/Kiosk/App/Models/Artist.swift @@ -15,7 +15,7 @@ final class Artist: NSObject, JSONAbleType { self.sortableID = sortableID } - static func fromJSON(json:[String: AnyObject]) -> Artist { + static func fromJSON(_ json:[String: AnyObject]) -> Artist { let json = JSON(json) let id = json["id"].stringValue diff --git a/Kiosk/App/Models/Artwork.swift b/Kiosk/App/Models/Artwork.swift index 6f530c66..7f16a8e8 100644 --- a/Kiosk/App/Models/Artwork.swift +++ b/Kiosk/App/Models/Artwork.swift @@ -4,15 +4,15 @@ import SwiftyJSON final class Artwork: NSObject, JSONAbleType { enum SoldStatus { - case NotSold - case Sold + case notSold + case sold - static func fromString(string: String) -> SoldStatus { - switch string.lowercaseString { + static func fromString(_ string: String) -> SoldStatus { + switch string.lowercased() { case "sold": - return .Sold + return .sold default: - return .NotSold + return .notSold } } } @@ -54,7 +54,7 @@ final class Artwork: NSObject, JSONAbleType { self.soldStatus = sold } - static func fromJSON(json: [String: AnyObject]) -> Artwork { + static func fromJSON(_ json: [String: AnyObject]) -> Artwork { let json = JSON(json) let id = json["id"].stringValue @@ -96,7 +96,7 @@ final class Artwork: NSObject, JSONAbleType { return artwork } - func updateWithValues(newArtwork: Artwork) { + func updateWithValues(_ newArtwork: Artwork) { // soldStatus is the only value we expect to change at runtime. soldStatus = newArtwork.soldStatus } @@ -106,7 +106,7 @@ final class Artwork: NSObject, JSONAbleType { } } -private func titleAndDateAttributedString(title: String, dateString: String) -> NSAttributedString { +private func titleAndDateAttributedString(_ title: String, dateString: String) -> NSAttributedString { let workTitle = title.isEmpty ? "Untitled" : title let workFont = UIFont.serifItalicFontWithSize(16) let attributedString = NSMutableAttributedString(string: workTitle, attributes: [NSFontAttributeName : workFont ]) diff --git a/Kiosk/App/Models/Bid.swift b/Kiosk/App/Models/Bid.swift index efa9f1d1..2cc2ebef 100644 --- a/Kiosk/App/Models/Bid.swift +++ b/Kiosk/App/Models/Bid.swift @@ -10,7 +10,7 @@ final class Bid: NSObject, JSONAbleType { self.amountCents = amountCents } - static func fromJSON(json:[String: AnyObject]) -> Bid { + static func fromJSON(_ json:[String: AnyObject]) -> Bid { let json = JSON(json) let id = json["id"].stringValue diff --git a/Kiosk/App/Models/Bidder.swift b/Kiosk/App/Models/Bidder.swift index 71d8c6dd..d01398cd 100644 --- a/Kiosk/App/Models/Bidder.swift +++ b/Kiosk/App/Models/Bidder.swift @@ -14,7 +14,7 @@ final class Bidder: NSObject, JSONAbleType { self.pin = pin } - static func fromJSON(json:[String: AnyObject]) -> Bidder { + static func fromJSON(_ json:[String: AnyObject]) -> Bidder { let json = JSON(json) let id = json["id"].stringValue diff --git a/Kiosk/App/Models/BidderPosition.swift b/Kiosk/App/Models/BidderPosition.swift index 942a40e9..5b88323f 100644 --- a/Kiosk/App/Models/BidderPosition.swift +++ b/Kiosk/App/Models/BidderPosition.swift @@ -6,16 +6,16 @@ final class BidderPosition: NSObject, JSONAbleType { let id: String let highestBid: Bid? let maxBidAmountCents: Int - let processedAt: NSDate? + let processedAt: Date? - init(id: String, highestBid:Bid?, maxBidAmountCents: Int, processedAt: NSDate?) { + init(id: String, highestBid:Bid?, maxBidAmountCents: Int, processedAt: Date?) { self.id = id self.highestBid = highestBid self.maxBidAmountCents = maxBidAmountCents self.processedAt = processedAt } - static func fromJSON(source:[String: AnyObject]) -> BidderPosition { + static func fromJSON(_ source:[String: AnyObject]) -> BidderPosition { let json = JSON(source) let formatter = ISO8601DateFormatter() diff --git a/Kiosk/App/Models/BuyersPremium.swift b/Kiosk/App/Models/BuyersPremium.swift index b1b4a692..f10864be 100644 --- a/Kiosk/App/Models/BuyersPremium.swift +++ b/Kiosk/App/Models/BuyersPremium.swift @@ -10,7 +10,7 @@ final class BuyersPremium: NSObject, JSONAbleType { self.name = name } - static func fromJSON(json: [String: AnyObject]) -> BuyersPremium { + static func fromJSON(_ json: [String: AnyObject]) -> BuyersPremium { let json = JSON(json) let id = json["id"].stringValue let name = json["name"].stringValue diff --git a/Kiosk/App/Models/Card.swift b/Kiosk/App/Models/Card.swift index f66112ae..c6cb110f 100644 --- a/Kiosk/App/Models/Card.swift +++ b/Kiosk/App/Models/Card.swift @@ -17,7 +17,7 @@ final class Card: NSObject, JSONAbleType { self.expirationYear = expirationYear } - static func fromJSON(json:[String: AnyObject]) -> Card { + static func fromJSON(_ json:[String: AnyObject]) -> Card { let json = JSON(json) let id = json["id"].stringValue diff --git a/Kiosk/App/Models/GenericError.swift b/Kiosk/App/Models/GenericError.swift index 5464751f..76ed6986 100644 --- a/Kiosk/App/Models/GenericError.swift +++ b/Kiosk/App/Models/GenericError.swift @@ -12,7 +12,7 @@ final class GenericError: NSObject, JSONAbleType { self.type = type } - static func fromJSON(json:[String: AnyObject]) -> GenericError { + static func fromJSON(_ json:[String: AnyObject]) -> GenericError { let json = JSON(json) let type = json["type"].stringValue diff --git a/Kiosk/App/Models/Image.swift b/Kiosk/App/Models/Image.swift index 7c8013ce..6261db67 100644 --- a/Kiosk/App/Models/Image.swift +++ b/Kiosk/App/Models/Image.swift @@ -29,7 +29,7 @@ final class Image: NSObject, JSONAbleType { self.isDefault = isDefault } - static func fromJSON(json:[String: AnyObject]) -> Image { + static func fromJSON(_ json:[String: AnyObject]) -> Image { let json = JSON(json) let id = json["id"].stringValue @@ -58,7 +58,7 @@ final class Image: NSObject, JSONAbleType { return Image(id: id, imageFormatString: imageFormatString, imageVersions: imageVersions, imageSize: imageSize, aspectRatio: aspectRatio, baseURL: baseURL, tileSize: tileSize, maxTiledHeight: maxTiledHeight, maxTiledWidth: maxTiledWidth, maxLevel: maxLevel, isDefault: isDefault) } - func thumbnailURL() -> NSURL? { + func thumbnailURL() -> URL? { let preferredVersions = { () -> Array in // For very tall images, the "medium" version looks terribad. // In the long-term, we have an issue to fix this for good: https://github.com/artsy/eidolon/issues/396 @@ -72,14 +72,14 @@ final class Image: NSObject, JSONAbleType { return urlFromPreferenceList(preferredVersions) } - func fullsizeURL() -> NSURL? { + func fullsizeURL() -> URL? { return urlFromPreferenceList(["larger", "large", "medium"]) } - private func urlFromPreferenceList(preferenceList: Array) -> NSURL? { + fileprivate func urlFromPreferenceList(_ preferenceList: Array) -> URL? { if let format = preferenceList.filter({ self.imageVersions.contains($0) }).first { - let path = NSString(string: self.imageFormatString).stringByReplacingOccurrencesOfString(":version", withString: format) - return NSURL(string: path) + let path = NSString(string: self.imageFormatString).replacingOccurrences(of: ":version", with: format) + return URL(string: path) } return nil } diff --git a/Kiosk/App/Models/Location.swift b/Kiosk/App/Models/Location.swift index ee1a4dcc..9f73f057 100644 --- a/Kiosk/App/Models/Location.swift +++ b/Kiosk/App/Models/Location.swift @@ -18,7 +18,7 @@ final class Location: NSObject, JSONAbleType { self.postalCode = postalCode } - static func fromJSON(json: [String: AnyObject]) -> Location { + static func fromJSON(_ json: [String: AnyObject]) -> Location { let json = JSON(json) let address = json["address"].stringValue diff --git a/Kiosk/App/Models/Sale.swift b/Kiosk/App/Models/Sale.swift index 7acf5182..b850f2e9 100644 --- a/Kiosk/App/Models/Sale.swift +++ b/Kiosk/App/Models/Sale.swift @@ -5,15 +5,15 @@ import SwiftyJSON final class Sale: NSObject, JSONAbleType { dynamic let id: String dynamic let isAuction: Bool - dynamic let startDate: NSDate - dynamic let endDate: NSDate + dynamic let startDate: Date + dynamic let endDate: Date dynamic let name: String dynamic var artworkCount: Int dynamic let auctionState: String dynamic var buyersPremium: BuyersPremium? - init(id: String, name: String, isAuction: Bool, startDate: NSDate, endDate: NSDate, artworkCount: Int, state: String) { + init(id: String, name: String, isAuction: Bool, startDate: Date, endDate: Date, artworkCount: Int, state: String) { self.id = id self.name = name self.isAuction = isAuction @@ -23,7 +23,7 @@ final class Sale: NSObject, JSONAbleType { self.auctionState = state } - static func fromJSON(json:[String: AnyObject]) -> Sale { + static func fromJSON(_ json:[String: AnyObject]) -> Sale { let json = JSON(json) let formatter = ISO8601DateFormatter() @@ -44,8 +44,8 @@ final class Sale: NSObject, JSONAbleType { return sale } - func isActive(systemTime:SystemTime) -> Bool { + func isActive(_ systemTime:SystemTime) -> Bool { let now = systemTime.date() - return now.earlierDate(startDate) == startDate && now.laterDate(endDate) == endDate + return (now as NSDate).earlierDate(startDate) == startDate && (now as NSDate).laterDate(endDate) == endDate } } diff --git a/Kiosk/App/Models/SaleArtwork.swift b/Kiosk/App/Models/SaleArtwork.swift index e775bc74..918d65c0 100644 --- a/Kiosk/App/Models/SaleArtwork.swift +++ b/Kiosk/App/Models/SaleArtwork.swift @@ -10,7 +10,7 @@ enum ReserveStatus: String { return self == .ReserveNotMet } - static func initOrDefault (rawValue: String?) -> ReserveStatus { + static func initOrDefault (_ rawValue: String?) -> ReserveStatus { return ReserveStatus(rawValue: rawValue ?? "") ?? .NoReserve } } @@ -52,7 +52,7 @@ final class SaleArtwork: NSObject, JSONAbleType { return SaleArtworkViewModel(saleArtwork: self) }() - static func fromJSON(json: [String: AnyObject]) -> SaleArtwork { + static func fromJSON(_ json: [String: AnyObject]) -> SaleArtwork { let json = JSON(json) let id = json["id"].stringValue let artworkDict = json["artwork"].object as! [String: AnyObject] @@ -79,7 +79,7 @@ final class SaleArtwork: NSObject, JSONAbleType { return saleArtwork } - func updateWithValues(newSaleArtwork: SaleArtwork) { + func updateWithValues(_ newSaleArtwork: SaleArtwork) { saleHighestBid = newSaleArtwork.saleHighestBid auctionID = newSaleArtwork.auctionID openingBidCents = newSaleArtwork.openingBidCents @@ -96,9 +96,9 @@ final class SaleArtwork: NSObject, JSONAbleType { } } -func createDollarFormatter() -> NSNumberFormatter { - let formatter = NSNumberFormatter() - formatter.numberStyle = NSNumberFormatterStyle.CurrencyStyle +func createDollarFormatter() -> NumberFormatter { + let formatter = NumberFormatter() + formatter.numberStyle = NumberFormatter.Style.currency // This is always dollars, so let's make sure that's how it shows up // regardless of locale. @@ -111,4 +111,4 @@ func createDollarFormatter() -> NSNumberFormatter { func ==(lhs: SaleArtwork, rhs: SaleArtwork) -> Bool { return lhs.id == rhs.id -} \ No newline at end of file +} diff --git a/Kiosk/App/Models/SaleArtworkViewModel.swift b/Kiosk/App/Models/SaleArtworkViewModel.swift index dd920a26..3a8d79f9 100644 --- a/Kiosk/App/Models/SaleArtworkViewModel.swift +++ b/Kiosk/App/Models/SaleArtworkViewModel.swift @@ -4,7 +4,7 @@ import RxSwift private let kNoBidsString = "" class SaleArtworkViewModel: NSObject { - private let saleArtwork: SaleArtwork + fileprivate let saleArtwork: SaleArtwork init (saleArtwork: SaleArtwork) { self.saleArtwork = saleArtwork @@ -20,23 +20,23 @@ extension SaleArtworkViewModel { var estimateString: String { // Default to estimateCents if let estimateCents = saleArtwork.estimateCents { - let dollars = NSNumberFormatter.currencyStringForCents(estimateCents) + let dollars = NumberFormatter.currencyString(forCents: estimateCents as NSNumber!) return "Estimate: \(dollars)" } // Try to extract non-nil low/high estimates. Return a default otherwise. switch (saleArtwork.lowEstimateCents, saleArtwork.highEstimateCents) { - case let (.Some(lowCents), .Some(highCents)): - let lowDollars = NSNumberFormatter.currencyStringForCents(lowCents) - let highDollars = NSNumberFormatter.currencyStringForCents(highCents) + case let (.some(lowCents), .some(highCents)): + let lowDollars = NumberFormatter.currencyString(forCents: lowCents as NSNumber!) + let highDollars = NumberFormatter.currencyString(forCents: highCents as NSNumber!) return "Estimate: \(lowDollars)–\(highDollars)" default: return "No Estimate" } } - var thumbnailURL: NSURL? { - return saleArtwork.artwork.defaultImage?.thumbnailURL() + var thumbnailURL: URL? { + return saleArtwork.artwork.defaultImage?.thumbnailURL() as URL? } var thumbnailAspectRatio: CGFloat? { @@ -59,7 +59,7 @@ extension SaleArtworkViewModel { func numberOfBids() -> Observable { return saleArtwork.rx_observe(NSNumber.self, "bidCount").map { optionalBidCount -> String in - guard let bidCount = optionalBidCount where bidCount.intValue > 0 else { + guard let bidCount = optionalBidCount , bidCount.intValue > 0 else { return kNoBidsString } @@ -120,7 +120,7 @@ extension SaleArtworkViewModel { } - func currentBid(prefix prefix: String = "", missingPrefix: String = "") -> Observable { + func currentBid(prefix: String = "", missingPrefix: String = "") -> Observable { return saleArtwork.rx_observe(NSNumber.self, "highestBidCents").map { [weak self] highestBidCents in if let currentBidCents = highestBidCents as? Int { return "\(prefix)\(NSNumberFormatter.currencyStringForCents(currentBidCents))" diff --git a/Kiosk/App/Models/SystemTime.swift b/Kiosk/App/Models/SystemTime.swift index 1c8a8e5f..8880df49 100644 --- a/Kiosk/App/Models/SystemTime.swift +++ b/Kiosk/App/Models/SystemTime.swift @@ -3,12 +3,12 @@ import ISO8601DateFormatter import RxSwift class SystemTime { - var systemTimeInterval: NSTimeInterval? = nil + var systemTimeInterval: TimeInterval? = nil init () {} - func sync(provider: Networking) -> Observable { - let endpoint: ArtsyAPI = ArtsyAPI.SystemTime + func sync(_ provider: Networking) -> Observable { + let endpoint: ArtsyAPI = ArtsyAPI.systemTime return provider.request(endpoint) .filterSuccessfulStatusCodes() @@ -27,10 +27,10 @@ class SystemTime { return systemTimeInterval != nil } - func date() -> NSDate { - let now = NSDate() + func date() -> Date { + let now = Date() if let systemTimeInterval = systemTimeInterval { - return now.dateByAddingTimeInterval(-systemTimeInterval) + return now.addingTimeInterval(-systemTimeInterval) } else { return now } diff --git a/Kiosk/App/Models/User.swift b/Kiosk/App/Models/User.swift index 9bbff46f..80321bb3 100644 --- a/Kiosk/App/Models/User.swift +++ b/Kiosk/App/Models/User.swift @@ -21,7 +21,7 @@ final class User: NSObject, JSONAbleType { self.location = location } - static func fromJSON(json: [String: AnyObject]) -> User { + static func fromJSON(_ json: [String: AnyObject]) -> User { let json = JSON(json) let id = json["id"].stringValue diff --git a/Kiosk/App/NSErrorExtensions.swift b/Kiosk/App/NSErrorExtensions.swift index c151d2a5..8cc1d084 100644 --- a/Kiosk/App/NSErrorExtensions.swift +++ b/Kiosk/App/NSErrorExtensions.swift @@ -8,10 +8,10 @@ extension NSError { let error = GenericError.fromJSON(errorJSON) return "\(error.message) - \(error.detail) + \(error.detail)" } else if let response = userInfo["data"] as? Response { - let stringData = NSString(data: response.data, encoding: NSUTF8StringEncoding) + let stringData = NSString(data: response.data, encoding: String.Encoding.utf8) return "Status Code: \(response.statusCode), Data Length: \(response.data.length), String Data: \(stringData)" } - return "\(userInfo)" + return "\(userInfo)" as NSString } -} \ No newline at end of file +} diff --git a/Kiosk/App/Networking/APIKeys.swift b/Kiosk/App/Networking/APIKeys.swift index edaafdee..563f2a01 100644 --- a/Kiosk/App/Networking/APIKeys.swift +++ b/Kiosk/App/Networking/APIKeys.swift @@ -11,7 +11,7 @@ struct APIKeys { // MARK: Shared Keys - private struct SharedKeys { + fileprivate struct SharedKeys { static var instance = APIKeys() } diff --git a/Kiosk/App/Networking/ArtsyAPI.swift b/Kiosk/App/Networking/ArtsyAPI.swift index 71058212..35b81b8c 100644 --- a/Kiosk/App/Networking/ArtsyAPI.swift +++ b/Kiosk/App/Networking/ArtsyAPI.swift @@ -8,153 +8,153 @@ protocol ArtsyAPIType { } enum ArtsyAPI { - case XApp - case XAuth(email: String, password: String) - case TrustToken(number: String, auctionPIN: String) + case xApp + case xAuth(email: String, password: String) + case trustToken(number: String, auctionPIN: String) - case SystemTime - case Ping + case systemTime + case ping - case Artwork(id: String) - case Artist(id: String) + case artwork(id: String) + case artist(id: String) - case Auctions - case AuctionListings(id: String, page: Int, pageSize: Int) - case AuctionInfo(auctionID: String) - case AuctionInfoForArtwork(auctionID: String, artworkID: String) - case FindBidderRegistration(auctionID: String, phone: String) - case ActiveAuctions + case auctions + case auctionListings(id: String, page: Int, pageSize: Int) + case auctionInfo(auctionID: String) + case auctionInfoForArtwork(auctionID: String, artworkID: String) + case findBidderRegistration(auctionID: String, phone: String) + case activeAuctions - case CreateUser(email: String, password: String, phone: String, postCode: String, name: String) + case createUser(email: String, password: String, phone: String, postCode: String, name: String) - case BidderDetailsNotification(auctionID: String, identifier: String) + case bidderDetailsNotification(auctionID: String, identifier: String) - case LostPasswordNotification(email: String) - case FindExistingEmailRegistration(email: String) + case lostPasswordNotification(email: String) + case findExistingEmailRegistration(email: String) } enum ArtsyAuthenticatedAPI { - case MyCreditCards - case CreatePINForBidder(bidderID: String) - case RegisterToBid(auctionID: String) - case MyBiddersForAuction(auctionID: String) - case MyBidPositionsForAuctionArtwork(auctionID: String, artworkID: String) - case MyBidPosition(id: String) - case FindMyBidderRegistration(auctionID: String) - case PlaceABid(auctionID: String, artworkID: String, maxBidCents: String) - - case UpdateMe(email: String, phone: String, postCode: String, name: String) - case RegisterCard(stripeToken: String, swiped: Bool) - case Me + case myCreditCards + case createPINForBidder(bidderID: String) + case registerToBid(auctionID: String) + case myBiddersForAuction(auctionID: String) + case myBidPositionsForAuctionArtwork(auctionID: String, artworkID: String) + case myBidPosition(id: String) + case findMyBidderRegistration(auctionID: String) + case placeABid(auctionID: String, artworkID: String, maxBidCents: String) + + case updateMe(email: String, phone: String, postCode: String, name: String) + case registerCard(stripeToken: String, swiped: Bool) + case me } extension ArtsyAPI : TargetType, ArtsyAPIType { var path: String { switch self { - case .XApp: + case .xApp: return "/api/v1/xapp_token" - case .XAuth: + case .xAuth: return "/oauth2/access_token" - case AuctionInfo(let id): + case .auctionInfo(let id): return "/api/v1/sale/\(id)" - case Auctions: + case .auctions: return "/api/v1/sales" - case AuctionListings(let id, _, _): + case .auctionListings(let id, _, _): return "/api/v1/sale/\(id)/sale_artworks" - case AuctionInfoForArtwork(let auctionID, let artworkID): + case .auctionInfoForArtwork(let auctionID, let artworkID): return "/api/v1/sale/\(auctionID)/sale_artwork/\(artworkID)" - case SystemTime: + case .systemTime: return "/api/v1/system/time" - case Ping: + case .ping: return "/api/v1/system/ping" - case FindBidderRegistration: + case .findBidderRegistration: return "/api/v1/bidder" - case ActiveAuctions: + case .activeAuctions: return "/api/v1/sales" - case CreateUser: + case .createUser: return "/api/v1/user" - case Artwork(let id): + case .artwork(let id): return "/api/v1/artwork/\(id)" - case Artist(let id): + case .artist(let id): return "/api/v1/artist/\(id)" - case TrustToken: + case .trustToken: return "/api/v1/me/trust_token" - case BidderDetailsNotification: + case .bidderDetailsNotification: return "/api/v1/bidder/bidding_details_notification" - case LostPasswordNotification: + case .lostPasswordNotification: return "/api/v1/users/send_reset_password_instructions" - case FindExistingEmailRegistration: + case .findExistingEmailRegistration: return "/api/v1/user" } } var base: String { return AppSetup.sharedState.useStaging ? "https://stagingapi.artsy.net" : "https://api.artsy.net" } - var baseURL: NSURL { return NSURL(string: base)! } + var baseURL: URL { return URL(string: base)! } var parameters: [String: AnyObject]? { switch self { - case XAuth(let email, let password): + case .xAuth(let email, let password): return [ - "client_id": APIKeys.sharedKeys.key ?? "", - "client_secret": APIKeys.sharedKeys.secret ?? "", - "email": email, - "password": password, + "client_id": APIKeys.sharedKeys.key as AnyObject? ?? "" as AnyObject, + "client_secret": APIKeys.sharedKeys.secret as AnyObject? ?? "" as AnyObject, + "email": email as AnyObject, + "password": password as AnyObject, "grant_type": "credentials" ] - case XApp: - return ["client_id": APIKeys.sharedKeys.key ?? "", - "client_secret": APIKeys.sharedKeys.secret ?? ""] + case .xApp: + return ["client_id": APIKeys.sharedKeys.key as AnyObject? ?? "" as AnyObject, + "client_secret": APIKeys.sharedKeys.secret as AnyObject? ?? "" as AnyObject] - case Auctions: - return ["is_auction": "true"] + case .auctions: + return ["is_auction": "true" as AnyObject] - case TrustToken(let number, let auctionID): - return ["number": number, "auction_pin": auctionID] + case .trustToken(let number, let auctionID): + return ["number": number as AnyObject, "auction_pin": auctionID as AnyObject] - case CreateUser(let email, let password,let phone,let postCode, let name): + case .createUser(let email, let password,let phone,let postCode, let name): return [ - "email": email, "password": password, - "phone": phone, "name": name, + "email": email as AnyObject, "password": password as AnyObject, + "phone": phone as AnyObject, "name": name as AnyObject, "location": [ "postal_code": postCode ] ] - case BidderDetailsNotification(let auctionID, let identifier): - return ["sale_id": auctionID, "identifier": identifier] + case .bidderDetailsNotification(let auctionID, let identifier): + return ["sale_id": auctionID as AnyObject, "identifier": identifier as AnyObject] - case LostPasswordNotification(let email): - return ["email": email] + case .lostPasswordNotification(let email): + return ["email": email as AnyObject] - case FindExistingEmailRegistration(let email): - return ["email": email] + case .findExistingEmailRegistration(let email): + return ["email": email as AnyObject] - case FindBidderRegistration(let auctionID, let phone): - return ["sale_id": auctionID, "number": phone] + case .findBidderRegistration(let auctionID, let phone): + return ["sale_id": auctionID as AnyObject, "number": phone as AnyObject] - case AuctionListings(_, let page, let pageSize): - return ["size": pageSize, "page": page] + case .auctionListings(_, let page, let pageSize): + return ["size": pageSize as AnyObject, "page": page as AnyObject] - case ActiveAuctions: - return ["is_auction": true, "live": true] + case .activeAuctions: + return ["is_auction": true as AnyObject, "live": true as AnyObject] default: return nil @@ -163,71 +163,71 @@ extension ArtsyAPI : TargetType, ArtsyAPIType { var method: Moya.Method { switch self { - case .LostPasswordNotification, - .CreateUser: + case .lostPasswordNotification, + .createUser: return .POST - case .FindExistingEmailRegistration: + case .findExistingEmailRegistration: return .HEAD - case .BidderDetailsNotification: + case .bidderDetailsNotification: return .PUT default: return .GET } } - var sampleData: NSData { + var sampleData: Data { switch self { - case XApp: + case .xApp: return stubbedResponse("XApp") - case XAuth: + case .xAuth: return stubbedResponse("XAuth") - case TrustToken: + case .trustToken: return stubbedResponse("XAuth") - case Auctions: + case .auctions: return stubbedResponse("Auctions") - case AuctionListings: + case .auctionListings: return stubbedResponse("AuctionListings") - case SystemTime: + case .systemTime: return stubbedResponse("SystemTime") - case ActiveAuctions: + case .activeAuctions: return stubbedResponse("ActiveAuctions") - case CreateUser: + case .createUser: return stubbedResponse("Me") - case Artwork: + case .artwork: return stubbedResponse("Artwork") - case Artist: + case .artist: return stubbedResponse("Artist") - case AuctionInfo: + case .auctionInfo: return stubbedResponse("AuctionInfo") // This API returns a 302, so stubbed response isn't valid - case FindBidderRegistration: + case .findBidderRegistration: return stubbedResponse("Me") - case BidderDetailsNotification: + case .bidderDetailsNotification: return stubbedResponse("RegisterToBid") - case LostPasswordNotification: + case .lostPasswordNotification: return stubbedResponse("ForgotPassword") - case FindExistingEmailRegistration: + case .findExistingEmailRegistration: return stubbedResponse("ForgotPassword") - case AuctionInfoForArtwork: + case .auctionInfoForArtwork: return stubbedResponse("AuctionInfoForArtwork") - case Ping: + case .ping: return stubbedResponse("Ping") } @@ -235,8 +235,8 @@ extension ArtsyAPI : TargetType, ArtsyAPIType { var addXAuth: Bool { switch self { - case .XApp: return false - case .XAuth: return false + case .xApp: return false + case .xAuth: return false default: return true } } @@ -246,74 +246,74 @@ extension ArtsyAuthenticatedAPI: TargetType, ArtsyAPIType { var path: String { switch self { - case RegisterToBid: + case .registerToBid: return "/api/v1/bidder" - case MyCreditCards: + case .myCreditCards: return "/api/v1/me/credit_cards" - case CreatePINForBidder(let bidderID): + case .createPINForBidder(let bidderID): return "/api/v1/bidder/\(bidderID)/pin" - case Me: + case .me: return "/api/v1/me" - case UpdateMe: + case .updateMe: return "/api/v1/me" - case MyBiddersForAuction: + case .myBiddersForAuction: return "/api/v1/me/bidders" - case MyBidPositionsForAuctionArtwork: + case .myBidPositionsForAuctionArtwork: return "/api/v1/me/bidder_positions" - case MyBidPosition(let id): + case .myBidPosition(let id): return "/api/v1/me/bidder_position/\(id)" - case FindMyBidderRegistration: + case .findMyBidderRegistration: return "/api/v1/me/bidders" - case PlaceABid: + case .placeABid: return "/api/v1/me/bidder_position" - case RegisterCard: + case .registerCard: return "/api/v1/me/credit_cards" } } var base: String { return AppSetup.sharedState.useStaging ? "https://stagingapi.artsy.net" : "https://api.artsy.net" } - var baseURL: NSURL { return NSURL(string: base)! } + var baseURL: URL { return URL(string: base)! } var parameters: [String: AnyObject]? { switch self { - case RegisterToBid(let auctionID): - return ["sale_id": auctionID] + case .registerToBid(let auctionID): + return ["sale_id": auctionID as AnyObject] - case MyBiddersForAuction(let auctionID): - return ["sale_id": auctionID] + case .myBiddersForAuction(let auctionID): + return ["sale_id": auctionID as AnyObject] - case PlaceABid(let auctionID, let artworkID, let maxBidCents): + case .placeABid(let auctionID, let artworkID, let maxBidCents): return [ - "sale_id": auctionID, - "artwork_id": artworkID, - "max_bid_amount_cents": maxBidCents + "sale_id": auctionID as AnyObject, + "artwork_id": artworkID as AnyObject, + "max_bid_amount_cents": maxBidCents as AnyObject ] - case FindMyBidderRegistration(let auctionID): - return ["sale_id": auctionID] + case .findMyBidderRegistration(let auctionID): + return ["sale_id": auctionID as AnyObject] - case UpdateMe(let email, let phone,let postCode, let name): + case .updateMe(let email, let phone,let postCode, let name): return [ - "email": email, "phone": phone, - "name": name, "location": [ "postal_code": postCode ] + "email": email as AnyObject, "phone": phone as AnyObject, + "name": name as AnyObject, "location": [ "postal_code": postCode ] ] - case RegisterCard(let token, let swiped): - return ["provider": "stripe", "token": token, "created_by_trusted_client": swiped] + case .registerCard(let token, let swiped): + return ["provider": "stripe" as AnyObject, "token": token as AnyObject, "created_by_trusted_client": swiped as AnyObject] - case MyBidPositionsForAuctionArtwork(let auctionID, let artworkID): - return ["sale_id": auctionID, "artwork_id": artworkID] + case .myBidPositionsForAuctionArtwork(let auctionID, let artworkID): + return ["sale_id": auctionID as AnyObject, "artwork_id": artworkID as AnyObject] default: return nil @@ -322,51 +322,51 @@ extension ArtsyAuthenticatedAPI: TargetType, ArtsyAPIType { var method: Moya.Method { switch self { - case .PlaceABid, - .RegisterCard, - .RegisterToBid, - .CreatePINForBidder: + case .placeABid, + .registerCard, + .registerToBid, + .createPINForBidder: return .POST - case .UpdateMe: + case .updateMe: return .PUT default: return .GET } } - var sampleData: NSData { + var sampleData: Data { switch self { - case CreatePINForBidder: + case .createPINForBidder: return stubbedResponse("CreatePINForBidder") - case MyCreditCards: + case .myCreditCards: return stubbedResponse("MyCreditCards") - case RegisterToBid: + case .registerToBid: return stubbedResponse("RegisterToBid") - case MyBiddersForAuction: + case .myBiddersForAuction: return stubbedResponse("MyBiddersForAuction") - case Me: + case .me: return stubbedResponse("Me") - case UpdateMe: + case .updateMe: return stubbedResponse("Me") - case PlaceABid: + case .placeABid: return stubbedResponse("CreateABid") - case FindMyBidderRegistration: + case .findMyBidderRegistration: return stubbedResponse("FindMyBidderRegistration") - case RegisterCard: + case .registerCard: return stubbedResponse("RegisterCard") - case MyBidPositionsForAuctionArtwork: + case .myBidPositionsForAuctionArtwork: return stubbedResponse("MyBidPositionsForAuctionArtwork") - case MyBidPosition: + case .myBidPosition: return stubbedResponse("MyBidPosition") } @@ -379,20 +379,20 @@ extension ArtsyAuthenticatedAPI: TargetType, ArtsyAPIType { // MARK: - Provider support -func stubbedResponse(filename: String) -> NSData! { +func stubbedResponse(_ filename: String) -> Data! { @objc class TestClass: NSObject { } - let bundle = NSBundle(forClass: TestClass.self) - let path = bundle.pathForResource(filename, ofType: "json") - return NSData(contentsOfFile: path!) + let bundle = Bundle(for: TestClass.self) + let path = bundle.path(forResource: filename, ofType: "json") + return (try? Data(contentsOf: URL(fileURLWithPath: path!))) } private extension String { var URLEscapedString: String { - return self.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLHostAllowedCharacterSet())! + return self.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlHostAllowed)! } } -func url(route: TargetType) -> String { +func url(_ route: TargetType) -> String { return route.baseURL.URLByAppendingPathComponent(route.path).absoluteString } diff --git a/Kiosk/App/Networking/NetworkLogger.swift b/Kiosk/App/Networking/NetworkLogger.swift index cdcf8d48..c7bebfa4 100644 --- a/Kiosk/App/Networking/NetworkLogger.swift +++ b/Kiosk/App/Networking/NetworkLogger.swift @@ -5,7 +5,7 @@ import Result /// Logs network activity (outgoing requests and incoming responses). class NetworkLogger: PluginType { - typealias Comparison = TargetType -> Bool + typealias Comparison = (TargetType) -> Bool let whitelist: Comparison let blacklist: Comparison @@ -15,13 +15,13 @@ class NetworkLogger: PluginType { self.blacklist = blacklist } - func willSendRequest(request: RequestType, target: TargetType) { + func willSendRequest(_ request: RequestType, target: TargetType) { // If the target is in the blacklist, don't log it. guard blacklist(target) == false else { return } logger.log("Sending request: \(request.request?.URL?.absoluteString ?? String())") } - func didReceiveResponse(result: Result, target: TargetType) { + func didReceiveResponse(_ result: Result, target: TargetType) { // If the target is in the blacklist, don't log it. guard blacklist(target) == false else { return } diff --git a/Kiosk/App/Networking/Networking.swift b/Kiosk/App/Networking/Networking.swift index 8e1ec8c2..c98918b3 100644 --- a/Kiosk/App/Networking/Networking.swift +++ b/Kiosk/App/Networking/Networking.swift @@ -4,9 +4,9 @@ import Moya import RxSwift import Alamofire -class OnlineProvider: RxMoyaProvider { +class OnlineProvider: RxMoyaProvider where Target: TargetType { - private let online: Observable + fileprivate let online: Observable init(endpointClosure: MoyaProvider.EndpointClosure = MoyaProvider.DefaultEndpointMapping, requestClosure: MoyaProvider.RequestClosure = MoyaProvider.DefaultRequestMapping, @@ -19,7 +19,7 @@ class OnlineProvider: RxMoyaProvider { super.init(endpointClosure: endpointClosure, requestClosure: requestClosure, stubClosure: stubClosure, manager: manager, plugins: plugins) } - override func request(token: Target) -> Observable { + override func request(_ token: Target) -> Observable { let actualRequest = super.request(token) return online .ignore(false) // Wait until we're online @@ -49,7 +49,7 @@ struct AuthorizedNetworking: NetworkingType { private extension Networking { /// Request to fetch and store new XApp token if the current token is missing or expired. - func XAppTokenRequest(defaults: NSUserDefaults) -> Observable { + func XAppTokenRequest(_ defaults: NSUserDefaults) -> Observable { var appToken = XAppToken(defaults: defaults) @@ -86,7 +86,7 @@ private extension Networking { // "Public" interfaces extension Networking { /// Request to fetch a given target. Ensures that valid XApp tokens exist before making request - func request(token: ArtsyAPI, defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()) -> Observable { + func request(_ token: ArtsyAPI, defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()) -> Observable { let actualRequest = self.provider.request(token) return self.XAppTokenRequest(defaults).flatMap { _ in actualRequest } @@ -94,7 +94,7 @@ extension Networking { } extension AuthorizedNetworking { - func request(token: ArtsyAuthenticatedAPI, defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()) -> Observable { + func request(_ token: ArtsyAuthenticatedAPI, defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()) -> Observable { return self.provider.request(token) } } @@ -106,7 +106,7 @@ extension NetworkingType { return Networking(provider: newProvider(plugins)) } - static func newAuthorizedNetworking(xAccessToken: String) -> AuthorizedNetworking { + static func newAuthorizedNetworking(_ xAccessToken: String) -> AuthorizedNetworking { return AuthorizedNetworking(provider: newProvider(authenticatedPlugins, xAccessToken: xAccessToken)) } @@ -118,7 +118,7 @@ extension NetworkingType { return AuthorizedNetworking(provider: OnlineProvider(endpointClosure: endpointsClosure(), requestClosure: Networking.endpointResolver(), stubClosure: MoyaProvider.ImmediatelyStub, online: .just(true))) } - static func endpointsClosure(xAccessToken: String? = nil) -> (T) -> Endpoint { + static func endpointsClosure(_ xAccessToken: String? = nil) -> (T) -> Endpoint where T: TargetType, T: ArtsyAPIType { return { target in var endpoint: Endpoint = Endpoint(URL: url(target), sampleResponseClosure: {.NetworkResponse(200, target.sampleData)}, method: target.method, parameters: target.parameters) @@ -166,7 +166,7 @@ extension NetworkingType { } // (Endpoint, NSURLRequest -> Void) -> Void - static func endpointResolver() -> MoyaProvider.RequestClosure { + static func endpointResolver() -> MoyaProvider.RequestClosure where T: TargetType { return { (endpoint, closure) in let request: NSMutableURLRequest = endpoint.urlRequest.mutableCopy() as! NSMutableURLRequest request.HTTPShouldHandleCookies = false @@ -175,7 +175,7 @@ extension NetworkingType { } } -private func newProvider(plugins: [PluginType], xAccessToken: String? = nil) -> OnlineProvider { +private func newProvider(_ plugins: [PluginType], xAccessToken: String? = nil) -> OnlineProvider where T: TargetType, T: ArtsyAPIType { return OnlineProvider(endpointClosure: Networking.endpointsClosure(xAccessToken), requestClosure: Networking.endpointResolver(), stubClosure: Networking.APIKeysBasedStubBehaviour, diff --git a/Kiosk/App/Networking/XAppToken.swift b/Kiosk/App/Networking/XAppToken.swift index 635c0dbb..9cdeb0f0 100644 --- a/Kiosk/App/Networking/XAppToken.swift +++ b/Kiosk/App/Networking/XAppToken.swift @@ -1,9 +1,9 @@ import Foundation -private extension NSDate { +private extension Date { var isInPast: Bool { - let now = NSDate() - return self.compare(now) == NSComparisonResult.OrderedAscending + let now = Date() + return self.compare(now) == ComparisonResult.orderedAscending } } @@ -15,14 +15,14 @@ struct XAppToken { // MARK: - Initializers - let defaults: NSUserDefaults + let defaults: UserDefaults - init(defaults: NSUserDefaults) { + init(defaults: UserDefaults) { self.defaults = defaults } init() { - self.defaults = NSUserDefaults.standardUserDefaults() + self.defaults = UserDefaults.standard } @@ -30,20 +30,20 @@ struct XAppToken { var token: String? { get { - let key = defaults.stringForKey(DefaultsKeys.TokenKey.rawValue) + let key = defaults.string(forKey: DefaultsKeys.TokenKey.rawValue) return key } set(newToken) { - defaults.setObject(newToken, forKey: DefaultsKeys.TokenKey.rawValue) + defaults.set(newToken, forKey: DefaultsKeys.TokenKey.rawValue) } } - var expiry: NSDate? { + var expiry: Date? { get { - return defaults.objectForKey(DefaultsKeys.TokenExpiry.rawValue) as? NSDate + return defaults.object(forKey: DefaultsKeys.TokenExpiry.rawValue) as? Date } set(newExpiry) { - defaults.setObject(newExpiry, forKey: DefaultsKeys.TokenExpiry.rawValue) + defaults.set(newExpiry, forKey: DefaultsKeys.TokenExpiry.rawValue) } } diff --git a/Kiosk/App/OfflineViewController.swift b/Kiosk/App/OfflineViewController.swift index 72aa832b..32274263 100644 --- a/Kiosk/App/OfflineViewController.swift +++ b/Kiosk/App/OfflineViewController.swift @@ -8,7 +8,7 @@ class OfflineViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - offlineLabel.font = UIFont.serifFontWithSize(49) - subtitleLabel.font = UIFont.serifItalicFontWithSize(32) + offlineLabel.font = UIFont.serifFont(withSize: 49) + subtitleLabel.font = UIFont.serifItalicFont(withSize: 32) } -} \ No newline at end of file +} diff --git a/Kiosk/App/SwiftExtensions.swift b/Kiosk/App/SwiftExtensions.swift index 314d1b6d..d64dd49b 100644 --- a/Kiosk/App/SwiftExtensions.swift +++ b/Kiosk/App/SwiftExtensions.swift @@ -1,9 +1,9 @@ extension Optional { var hasValue: Bool { switch self { - case .None: + case .none: return false - case .Some(_): + case .some(_): return true } } @@ -14,7 +14,7 @@ extension String { return UInt(self) } - func toUIntWithDefault(defaultValue: UInt) -> UInt { + func toUInt(withDefault defaultValue: UInt) -> UInt { return UInt(self) ?? defaultValue } } @@ -43,9 +43,9 @@ extension Set: Occupiable { } extension Optional where Wrapped: Occupiable { var isNilOrEmpty: Bool { switch self { - case .None: + case .none: return true - case .Some(let value): + case .some(let value): return value.isEmpty } } diff --git a/Kiosk/App/UIViewControllerExtensions.swift b/Kiosk/App/UIViewControllerExtensions.swift index 4fd0bc6d..6c5ddff8 100644 --- a/Kiosk/App/UIViewControllerExtensions.swift +++ b/Kiosk/App/UIViewControllerExtensions.swift @@ -11,8 +11,8 @@ extension UIViewController { /// Short hand syntax for performing a segue with a known hardcoded identity - func performSegue(identifier:SegueIdentifier) { - performSegueWithIdentifier(identifier.rawValue, sender: self) + func performSegue(_ identifier:SegueIdentifier) { + self.performSegue(withIdentifier: identifier.rawValue, sender: self) } func fulfillmentNav() -> FulfillmentNavigationController { @@ -20,12 +20,12 @@ extension UIViewController { } func fulfillmentContainer() -> FulfillmentContainerViewController? { - return fulfillmentNav().parentViewController as? FulfillmentContainerViewController + return fulfillmentNav().parent as? FulfillmentContainerViewController } - func findChildViewControllerOfType(klass: AnyClass) -> UIViewController? { + func findChildViewControllerOfType(_ klass: AnyClass) -> UIViewController? { for child in childViewControllers { - if child.isKindOfClass(klass) { + if child.isKind(of: klass) { return child } } diff --git a/Kiosk/App/UIViewSubclassesErrorExtensions.swift b/Kiosk/App/UIViewSubclassesErrorExtensions.swift index 6513fb1d..b1da2841 100644 --- a/Kiosk/App/UIViewSubclassesErrorExtensions.swift +++ b/Kiosk/App/UIViewSubclassesErrorExtensions.swift @@ -2,20 +2,20 @@ import UIKit extension Button { - func flashError(message:String) { - let originalTitle = self.titleForState(.Normal) + func flashError(_ message:String) { + let originalTitle = self.title(for: UIControlState()) - setTitleColor(.whiteColor(), forState: .Disabled) - setBackgroundColor(.artsyRed(), forState: .Disabled, animated: true) - setBorderColor(.artsyRed(), forState: .Disabled, animated: true) + setTitleColor(.white(), for: .disabled) + setBackgroundColor(.artsyRed(), for: .disabled, animated: true) + setBorderColor(.artsyRed(), for: .disabled, animated: true) - setTitle(message.uppercaseString, forState: .Disabled) + setTitle(message.uppercased(), for: .disabled) delayToMainThread(2) { - self.setTitleColor(.artsyMediumGrey(), forState: .Disabled) - self.setBackgroundColor(.whiteColor(), forState: .Disabled, animated: true) - self.setTitle(originalTitle, forState: .Disabled) - self.setBorderColor(.artsyMediumGrey(), forState: .Disabled, animated: true) + self.setTitleColor(.artsyMediumGrey(), for: .disabled) + self.setBackgroundColor(.white(), for: .disabled, animated: true) + self.setTitle(originalTitle, for: .disabled) + self.setBorderColor(.artsyMediumGrey(), for: .disabled, animated: true) } } } @@ -28,4 +28,4 @@ extension TextField { self.setBorderColor(.artsyPurple()) } } -} \ No newline at end of file +} diff --git a/Kiosk/App/Views/Button Subclasses/Button.swift b/Kiosk/App/Views/Button Subclasses/Button.swift index 0c724ec2..a580e12e 100644 --- a/Kiosk/App/Views/Button Subclasses/Button.swift +++ b/Kiosk/App/Views/Button Subclasses/Button.swift @@ -6,56 +6,56 @@ class Button: ARFlatButton { override func setup() { super.setup() - setTitleShadowColor(.clearColor(), forState: .Normal) - setTitleShadowColor(.clearColor(), forState: .Highlighted) - setTitleShadowColor(.clearColor(), forState: .Disabled) + setTitleShadowColor(.clear(), for: UIControlState()) + setTitleShadowColor(.clear(), for: .highlighted) + setTitleShadowColor(.clear(), for: .disabled) shouldDimWhenDisabled = false; } } class ActionButton: Button { - override func intrinsicContentSize() -> CGSize { - return CGSizeMake(UIViewNoIntrinsicMetric, ButtonHeight) + override var intrinsicContentSize : CGSize { + return CGSize(width: UIViewNoIntrinsicMetric, height: ButtonHeight) } override func setup() { super.setup() - setBorderColor(.blackColor(), forState: .Normal, animated: false) - setBorderColor(.artsyPurple(), forState: .Highlighted, animated: false) - setBorderColor(.artsyMediumGrey(), forState: .Disabled, animated: false) + setBorderColor(.black(), for: UIControlState(), animated: false) + setBorderColor(.artsyPurple(), for: .highlighted, animated: false) + setBorderColor(.artsyMediumGrey(), for: .disabled, animated: false) - setBackgroundColor(.blackColor(), forState: .Normal, animated: false) - setBackgroundColor(.artsyPurple(), forState: .Highlighted, animated: false) - setBackgroundColor(.whiteColor(), forState: .Disabled, animated: false) + setBackgroundColor(.black(), for: UIControlState(), animated: false) + setBackgroundColor(.artsyPurple(), for: .highlighted, animated: false) + setBackgroundColor(.white(), for: .disabled, animated: false) - setTitleColor(.whiteColor(), forState:.Normal) - setTitleColor(.whiteColor(), forState:.Highlighted) - setTitleColor(.artsyHeavyGrey(), forState:.Disabled) + setTitleColor(.white(), for:UIControlState()) + setTitleColor(.white(), for:.highlighted) + setTitleColor(.artsyHeavyGrey(), for:.disabled) } } class SecondaryActionButton: Button { - override func intrinsicContentSize() -> CGSize { - return CGSizeMake(UIViewNoIntrinsicMetric, ButtonHeight) + override var intrinsicContentSize : CGSize { + return CGSize(width: UIViewNoIntrinsicMetric, height: ButtonHeight) } override func setup() { super.setup() - setBorderColor(.artsyMediumGrey(), forState: .Normal, animated: false) - setBorderColor(.artsyPurple(), forState: .Highlighted, animated: false) - setBorderColor(.artsyLightGrey(), forState: .Disabled, animated: false) + setBorderColor(.artsyMediumGrey(), for: UIControlState(), animated: false) + setBorderColor(.artsyPurple(), for: .highlighted, animated: false) + setBorderColor(.artsyLightGrey(), for: .disabled, animated: false) - setBackgroundColor(.whiteColor(), forState: .Normal, animated: false) - setBackgroundColor(.artsyPurple(), forState: .Highlighted, animated: false) - setBackgroundColor(.whiteColor(), forState: .Disabled, animated: false) + setBackgroundColor(.white(), for: UIControlState(), animated: false) + setBackgroundColor(.artsyPurple(), for: .highlighted, animated: false) + setBackgroundColor(.white(), for: .disabled, animated: false) - setTitleColor(.blackColor(), forState:.Normal) - setTitleColor(.whiteColor(), forState:.Highlighted) - setTitleColor(.artsyHeavyGrey(), forState:.Disabled) + setTitleColor(.black(), for:UIControlState()) + setTitleColor(.white(), for:.highlighted) + setTitleColor(.artsyHeavyGrey(), for:.disabled) } } @@ -66,15 +66,15 @@ class KeypadButton: Button { super.setup() shouldAnimateStateChange = false; layer.borderWidth = 0 - setBackgroundColor(.blackColor(), forState: .Highlighted, animated: false) - setBackgroundColor(.whiteColor(), forState: .Normal, animated: false) + setBackgroundColor(.black(), for: .highlighted, animated: false) + setBackgroundColor(.white(), for: UIControlState(), animated: false) } } class LargeKeypadButton: KeypadButton { override func setup() { super.setup() - self.titleLabel!.font = UIFont.sansSerifFontWithSize(20) + self.titleLabel!.font = UIFont.sansSerifFont(withSize: 20) } } @@ -82,17 +82,17 @@ class MenuButton: ARMenuButton { override func setup() { super.setup() if let titleLabel = titleLabel { - titleLabel.font = titleLabel.font.fontWithSize(12) + titleLabel.font = titleLabel.font.withSize(12) } } override func layoutSubviews() { super.layoutSubviews() - if let titleLabel = titleLabel { self.bringSubviewToFront(titleLabel) } - if let imageView = imageView { self.bringSubviewToFront(imageView) } + if let titleLabel = titleLabel { self.bringSubview(toFront: titleLabel) } + if let imageView = imageView { self.bringSubview(toFront: imageView) } } - override func intrinsicContentSize() -> CGSize { + override var intrinsicContentSize : CGSize { return CGSize(width: 45, height: 45) } } diff --git a/Kiosk/App/Views/RegisterFlowView.swift b/Kiosk/App/Views/RegisterFlowView.swift index 4ffdbb83..a114edc6 100644 --- a/Kiosk/App/Views/RegisterFlowView.swift +++ b/Kiosk/App/Views/RegisterFlowView.swift @@ -22,12 +22,12 @@ class RegisterFlowView: ORStackView { updateConstraints() } - private struct SubViewParams { + fileprivate struct SubViewParams { let title: String - let getters: Array String?> + let getters: Array<(NewUser) -> String?> } - private lazy var subViewParams: Array = { + fileprivate lazy var subViewParams: Array = { return [ [SubViewParams(title: "Mobile", getters: [{ $0.phoneNumber.value }])], [SubViewParams(title: "Email", getters: [{ $0.email.value }])], @@ -40,7 +40,7 @@ class RegisterFlowView: ORStackView { let user = details!.newUser removeAllSubviews() - for (i, subViewParam) in subViewParams.enumerate() { + for (i, subViewParam) in subViewParams.enumerated() { let itemView = ItemView(frame: bounds) itemView.createTitleViewWithTitle(subViewParam.title) @@ -69,7 +69,7 @@ class RegisterFlowView: ORStackView { bottomMarginHeight = 0 } - func pressed(sender: UIButton!) { + func pressed(_ sender: UIButton!) { highlightedIndex.value = sender.tag } @@ -81,39 +81,39 @@ class RegisterFlowView: ORStackView { titleLabel?.textColor = .artsyPurple() } - func createTitleViewWithTitle(title: String) { + func createTitleViewWithTitle(_ title: String) { let label = UILabel(frame: bounds) - label.font = UIFont.sansSerifFontWithSize(16) - label.text = title.uppercaseString + label.font = UIFont.sansSerifFont(withSize: 16) + label.text = title.uppercased() titleLabel = label addSubview(label) - label.constrainWidthToView(self, predicate: "0") - label.alignLeadingEdgeWithView(self, predicate: "0") - label.alignTopEdgeWithView(self, predicate: "0") + label.constrainWidth(to: self, predicate: "0") + label.alignLeadingEdge(with: self, predicate: "0") + label.alignTopEdge(with: self, predicate: "0") } - func createInfoLabel(info: String) { + func createInfoLabel(_ info: String) { let label = UILabel(frame: bounds) - label.font = UIFont.serifFontWithSize(16) + label.font = UIFont.serifFont(withSize: 16) label.text = info addSubview(label) - label.constrainWidthToView(self, predicate: "-52") - label.alignLeadingEdgeWithView(self, predicate: "0") - label.constrainTopSpaceToView(titleLabel!, predicate: "8") + label.constrainWidth(to: self, predicate: "-52") + label.alignLeadingEdge(with: self, predicate: "0") + label.constrainTopSpace(to: titleLabel!, predicate: "8") } - func createJumpToButtonAtIndex(index: NSInteger) -> UIButton { - let button = UIButton(type: .Custom) + func createJumpToButtonAtIndex(_ index: NSInteger) -> UIButton { + let button = UIButton(type: .custom) button.tag = index - button.setImage(UIImage(named: "edit_button"), forState: .Normal) - button.userInteractionEnabled = true - button.enabled = true + button.setImage(UIImage(named: "edit_button"), for: UIControlState()) + button.isUserInteractionEnabled = true + button.isEnabled = true addSubview(button) - button.alignTopEdgeWithView(self, predicate: "0") - button.alignTrailingEdgeWithView(self, predicate: "0") + button.alignTopEdge(with: self, predicate: "0") + button.alignTrailingEdge(with: self, predicate: "0") button.constrainWidth("36") button.constrainHeight("36") diff --git a/Kiosk/App/Views/SimulatorOnlyView.swift b/Kiosk/App/Views/SimulatorOnlyView.swift index 15550b78..43d46e6e 100644 --- a/Kiosk/App/Views/SimulatorOnlyView.swift +++ b/Kiosk/App/Views/SimulatorOnlyView.swift @@ -4,12 +4,12 @@ class DeveloperOnlyView: UIView { override func awakeFromNib() { // Show only if we're supposed to show AND we're on staging. - self.hidden = !(AppSetup.sharedState.showDebugButtons && AppSetup.sharedState.useStaging) + self.isHidden = !(AppSetup.sharedState.showDebugButtons && AppSetup.sharedState.useStaging) if let _ = NSClassFromString("XCTest") { // We are running in a test. - self.hidden = true + self.isHidden = true self.alpha = 0 } } -} \ No newline at end of file +} diff --git a/Kiosk/App/Views/Spinner.swift b/Kiosk/App/Views/Spinner.swift index 430636b3..6ed0c612 100644 --- a/Kiosk/App/Views/Spinner.swift +++ b/Kiosk/App/Views/Spinner.swift @@ -5,37 +5,37 @@ class Spinner: UIView { let rotationDuration = 0.9; func createSpinner() -> UIView { - let view = UIView(frame: CGRectMake(0, 0, 20, 5)) - view.backgroundColor = .blackColor() + let view = UIView(frame: CGRect(x: 0, y: 0, width: 20, height: 5)) + view.backgroundColor = .black() return view } override func awakeFromNib() { spinner = createSpinner() addSubview(spinner) - backgroundColor = .clearColor() + backgroundColor = .clear() animateN(Float.infinity) } override func layoutSubviews() { // .center uses frame - spinner.center = CGPointMake( CGRectGetWidth(bounds) / 2, CGRectGetHeight(bounds) / 2) + spinner.center = CGPoint( x: bounds.width / 2, y: bounds.height / 2) } - func animateN(times: Float) { + func animateN(_ times: Float) { let transformOffset = -1.01 * M_PI let transform = CATransform3DMakeRotation( CGFloat(transformOffset), 0, 0, 1); let rotationAnimation = CABasicAnimation(keyPath:"transform"); - rotationAnimation.toValue = NSValue(CATransform3D:transform) + rotationAnimation.toValue = NSValue(caTransform3D:transform) rotationAnimation.duration = rotationDuration; - rotationAnimation.cumulative = true; + rotationAnimation.isCumulative = true; rotationAnimation.repeatCount = Float(times); - layer.addAnimation(rotationAnimation, forKey:"spin"); + layer.add(rotationAnimation, forKey:"spin"); } - func animate(animate: Bool) { - let isAnimating = layer.animationForKey("spin") != nil + func animate(_ animate: Bool) { + let isAnimating = layer.animation(forKey: "spin") != nil if (isAnimating && !animate) { layer.removeAllAnimations() diff --git a/Kiosk/App/Views/SwitchView.swift b/Kiosk/App/Views/SwitchView.swift index 45f7e70b..18acc8c2 100644 --- a/Kiosk/App/Views/SwitchView.swift +++ b/Kiosk/App/Views/SwitchView.swift @@ -5,40 +5,40 @@ let SwitchViewBorderWidth: CGFloat = 2 class SwitchView: UIView { - private var _selectedIndex = Variable(0) + fileprivate var _selectedIndex = Variable(0) var selectedIndex: Observable { return _selectedIndex.asObservable() } var shouldAnimate = true - var animationDuration: NSTimeInterval = AnimationDuration.Short + var animationDuration: TimeInterval = AnimationDuration.Short - private let buttons: Array - private let selectionIndicator: UIView - private let topSelectionIndicator: UIView - private let bottomSelectionIndicator: UIView + fileprivate let buttons: Array + fileprivate let selectionIndicator: UIView + fileprivate let topSelectionIndicator: UIView + fileprivate let bottomSelectionIndicator: UIView - private let topBar = CALayer() - private let bottomBar = CALayer() + fileprivate let topBar = CALayer() + fileprivate let bottomBar = CALayer() var selectionConstraint: NSLayoutConstraint! init(buttonTitles: Array) { buttons = buttonTitles.map { (buttonTitle: String) -> UIButton in - let button = UIButton(type: .Custom) + let button = UIButton(type: .custom) - button.setTitle(buttonTitle, forState: .Normal) - button.setTitle(buttonTitle, forState: .Disabled) + button.setTitle(buttonTitle, for: UIControlState()) + button.setTitle(buttonTitle, for: .disabled) if let titleLabel = button.titleLabel { - titleLabel.font = UIFont.sansSerifFontWithSize(13) - titleLabel.backgroundColor = .whiteColor() - titleLabel.opaque = true + titleLabel.font = UIFont.sansSerifFont(withSize: 13) + titleLabel.backgroundColor = .white() + titleLabel.isOpaque = true } - button.backgroundColor = .whiteColor() - button.setTitleColor(.blackColor(), forState: .Disabled) - button.setTitleColor(.blackColor(), forState: .Selected) - button.setTitleColor(.artsyMediumGrey(), forState: .Normal) + button.backgroundColor = .white() + button.setTitleColor(.black(), for: .disabled) + button.setTitleColor(.black(), for: .selected) + button.setTitleColor(.artsyMediumGrey(), for: UIControlState()) return button } @@ -46,16 +46,16 @@ class SwitchView: UIView { topSelectionIndicator = UIView() bottomSelectionIndicator = UIView() - super.init(frame: CGRectZero) + super.init(frame: CGRect.zero) setup() } override func layoutSubviews() { super.layoutSubviews() - var rect = CGRectMake(0, 0, CGRectGetWidth(layer.bounds), SwitchViewBorderWidth) + var rect = CGRect(x: 0, y: 0, width: layer.bounds.width, height: SwitchViewBorderWidth) topBar.frame = rect - rect.origin.y = CGRectGetHeight(layer.bounds) - SwitchViewBorderWidth + rect.origin.y = layer.bounds.height - SwitchViewBorderWidth bottomBar.frame = rect } @@ -63,12 +63,12 @@ class SwitchView: UIView { self.init(buttonTitles: []) } - override func intrinsicContentSize() -> CGSize { + override var intrinsicContentSize : CGSize { return CGSize(width: UIViewNoIntrinsicMetric, height: 46) } - func selectedButton(button: UIButton!) { - let index = buttons.indexOf(button)! + func selectedButton(_ button: UIButton!) { + let index = buttons.index(of: button)! setSelectedIndex(index, animated: shouldAnimate) } @@ -85,7 +85,7 @@ class SwitchView: UIView { private extension SwitchView { func setup() { if let firstButton = buttons.first { - firstButton.enabled = false + firstButton.isEnabled = false } let widthPredicateMultiplier = "*\(widthMultiplier())" @@ -94,60 +94,60 @@ private extension SwitchView { let button = buttons[i] self.addSubview(button) - button.addTarget(self, action: #selector(SwitchView.selectedButton(_:)), forControlEvents: .TouchUpInside) + button.addTarget(self, action: #selector(SwitchView.selectedButton(_:)), for: .touchUpInside) - button.constrainWidthToView(self, predicate: widthPredicateMultiplier) + button.constrainWidth(to: self, predicate: widthPredicateMultiplier) if (i == 0) { - button.alignLeadingEdgeWithView(self, predicate: nil) + button.alignLeadingEdge(with: self, predicate: nil) } else { - button.constrainLeadingSpaceToView(buttons[i-1], predicate: nil) + button.constrainLeadingSpace(to: buttons[i-1], predicate: nil) } - button.alignTop("\(SwitchViewBorderWidth)", bottom: "\(-SwitchViewBorderWidth)", toView: self) + button.alignTop("\(SwitchViewBorderWidth)", bottom: "\(-SwitchViewBorderWidth)", to: self) } - topBar.backgroundColor = UIColor.artsyMediumGrey().CGColor - bottomBar.backgroundColor = UIColor.artsyMediumGrey().CGColor + topBar.backgroundColor = UIColor.artsyMediumGrey().cgColor + bottomBar.backgroundColor = UIColor.artsyMediumGrey().cgColor layer.addSublayer(topBar) layer.addSublayer(bottomBar) selectionIndicator.addSubview(topSelectionIndicator) selectionIndicator.addSubview(bottomSelectionIndicator) - topSelectionIndicator.backgroundColor = .blackColor() - bottomSelectionIndicator.backgroundColor = .blackColor() + topSelectionIndicator.backgroundColor = .black() + bottomSelectionIndicator.backgroundColor = .black() - topSelectionIndicator.alignTop("0", leading: "0", bottom: nil, trailing: "0", toView: selectionIndicator) - bottomSelectionIndicator.alignTop(nil, leading: "0", bottom: "0", trailing: "0", toView: selectionIndicator) + topSelectionIndicator.alignTop("0", leading: "0", bottom: nil, trailing: "0", to: selectionIndicator) + bottomSelectionIndicator.alignTop(nil, leading: "0", bottom: "0", trailing: "0", to: selectionIndicator) topSelectionIndicator.constrainHeight("\(SwitchViewBorderWidth)") bottomSelectionIndicator.constrainHeight("\(SwitchViewBorderWidth)") addSubview(selectionIndicator) - selectionIndicator.constrainWidthToView(self, predicate: widthPredicateMultiplier) - selectionIndicator.alignTop("0", bottom: "0", toView: self) + selectionIndicator.constrainWidth(to: self, predicate: widthPredicateMultiplier) + selectionIndicator.alignTop("0", bottom: "0", to: self) - selectionConstraint = selectionIndicator.alignLeadingEdgeWithView(self, predicate: nil).last! as! NSLayoutConstraint + selectionConstraint = selectionIndicator.alignLeadingEdge(with: self, predicate: nil).last! as! NSLayoutConstraint } func widthMultiplier() -> Float { return 1.0 / Float(buttons.count) } - func setSelectedIndex(index: Int) { + func setSelectedIndex(_ index: Int) { setSelectedIndex(index, animated: false) } - func setSelectedIndex(index: Int, animated: Bool) { - UIView.animateIf(shouldAnimate && animated, duration: animationDuration, options: .CurveEaseOut) { + func setSelectedIndex(_ index: Int, animated: Bool) { + UIView.animateIf(shouldAnimate && animated, duration: animationDuration, options: .curveEaseOut) { let button = self.buttons[index] self.buttons.forEach { (button: UIButton) in - button.enabled = true + button.isEnabled = true } - button.enabled = false + button.isEnabled = false // Set the x-position of the selection indicator as a fraction of the total width of the switch view according to which button was pressed. let multiplier = CGFloat(index) / CGFloat(self.buttons.count) @@ -155,9 +155,9 @@ private extension SwitchView { self.removeConstraint(self.selectionConstraint) // It's illegal to have a multiplier of zero, so if we're at index zero, we .just stick to the left side. if multiplier == 0 { - self.selectionConstraint = self.selectionIndicator.alignLeadingEdgeWithView(self, predicate: nil).last! as! NSLayoutConstraint + self.selectionConstraint = self.selectionIndicator.alignLeadingEdge(with: self, predicate: nil).last! as! NSLayoutConstraint } else { - self.selectionConstraint = NSLayoutConstraint(item: self.selectionIndicator, attribute: .Left, relatedBy: .Equal, toItem: self, attribute: .Right, multiplier: multiplier, constant: 0) + self.selectionConstraint = NSLayoutConstraint(item: self.selectionIndicator, attribute: .left, relatedBy: .equal, toItem: self, attribute: .right, multiplier: multiplier, constant: 0) } self.addConstraint(self.selectionConstraint) self.layoutIfNeeded() diff --git a/Kiosk/App/Views/Text Fields/CursorView.swift b/Kiosk/App/Views/Text Fields/CursorView.swift index 812ff945..0ebf6a7c 100644 --- a/Kiosk/App/Views/Text Fields/CursorView.swift +++ b/Kiosk/App/Views/Text Fields/CursorView.swift @@ -25,8 +25,8 @@ class CursorView: UIView { } func setupCursorLayer() { - cursorLayer.frame = CGRectMake(CGRectGetWidth(layer.frame)/2 - 1, 0, 2, CGRectGetHeight(layer.frame)) - cursorLayer.backgroundColor = UIColor.blackColor().CGColor + cursorLayer.frame = CGRect(x: layer.frame.width/2 - 1, y: 0, width: 2, height: layer.frame.height) + cursorLayer.backgroundColor = UIColor.black.cgColor cursorLayer.opacity = 0.0 } @@ -34,7 +34,7 @@ class CursorView: UIView { animate(Float.infinity) } - private func animate(times: Float) { + fileprivate func animate(_ times: Float) { let fade = CABasicAnimation() fade.duration = 0.5 fade.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) @@ -42,7 +42,7 @@ class CursorView: UIView { fade.autoreverses = true fade.fromValue = 0.0 fade.toValue = 1.0 - cursorLayer.addAnimation(fade, forKey: "opacity") + cursorLayer.add(fade, forKey: "opacity") } func stopAnimating() { diff --git a/Kiosk/App/Views/Text Fields/TextField.swift b/Kiosk/App/Views/Text Fields/TextField.swift index c0f58eae..c4c37c43 100644 --- a/Kiosk/App/Views/Text Fields/TextField.swift +++ b/Kiosk/App/Views/Text Fields/TextField.swift @@ -21,62 +21,62 @@ class TextField: UITextField { } func setup() { - borderStyle = .None + borderStyle = .none layer.cornerRadius = 0 layer.masksToBounds = true layer.borderWidth = 1 - tintColor = .blackColor() + tintColor = .black() stateChangedAnimated(false) setupEvents() } func setupEvents () { - addTarget(self, action: #selector(TextField.editingDidBegin(_:)), forControlEvents: .EditingDidBegin) - addTarget(self, action: #selector(TextField.editingDidFinish(_:)), forControlEvents: .EditingDidEnd) + addTarget(self, action: #selector(TextField.editingDidBegin(_:)), for: .editingDidBegin) + addTarget(self, action: #selector(TextField.editingDidFinish(_:)), for: .editingDidEnd) } - func editingDidBegin (sender: AnyObject) { + func editingDidBegin (_ sender: AnyObject) { stateChangedAnimated(shouldAnimateStateChange) } - func editingDidFinish(sender: AnyObject) { + func editingDidFinish(_ sender: AnyObject) { stateChangedAnimated(shouldAnimateStateChange) } - func stateChangedAnimated(animated: Bool) { - let newBorderColor = borderColorForState().CGColor + func stateChangedAnimated(_ animated: Bool) { + let newBorderColor = borderColorForState().cgColor if CGColorEqualToColor(newBorderColor, layer.borderColor) { return } if animated { let fade = CABasicAnimation() - if layer.borderColor == nil { layer.borderColor = UIColor.clearColor().CGColor } - fade.fromValue = self.layer.borderColor ?? UIColor.clearColor().CGColor + if layer.borderColor == nil { layer.borderColor = UIColor.clear.cgColor } + fade.fromValue = self.layer.borderColor ?? UIColor.clear.cgColor fade.toValue = newBorderColor fade.duration = AnimationDuration.Short - layer.addAnimation(fade, forKey: "borderColor") + layer.add(fade, forKey: "borderColor") } layer.borderColor = newBorderColor } func borderColorForState() -> UIColor { - if editing && shouldChangeColorWhenEditing { + if isEditing && shouldChangeColorWhenEditing { return .artsyPurple() } else { return .artsyMediumGrey() } } - func setBorderColor(color: UIColor){ - self.layer.borderColor = color.CGColor + func setBorderColor(_ color: UIColor){ + self.layer.borderColor = color.cgColor } - override func textRectForBounds(bounds: CGRect) -> CGRect { - return CGRectInset( bounds, 10, 0 ) + override func textRect(forBounds bounds: CGRect) -> CGRect { + return bounds.insetBy(dx: 10, dy: 0 ) } - override func editingRectForBounds(bounds: CGRect) -> CGRect { - return CGRectInset( bounds, 10 , 0 ) + override func editingRect(forBounds bounds: CGRect) -> CGRect { + return bounds.insetBy(dx: 10 , dy: 0 ) } } @@ -86,7 +86,7 @@ class SecureTextField: TextField { override var text: String! { get { - if editing { + if isEditing { return super.text } else { return actualText; @@ -105,22 +105,22 @@ class SecureTextField: TextField { override func setupEvents () { super.setupEvents() - addTarget(self, action: #selector(SecureTextField.editingDidChange(_:)), forControlEvents: .EditingChanged) + addTarget(self, action: #selector(SecureTextField.editingDidChange(_:)), for: .editingChanged) } - override func editingDidBegin (sender: AnyObject) { + override func editingDidBegin (_ sender: AnyObject) { super.editingDidBegin(sender) - secureTextEntry = true + isSecureTextEntry = true actualText = text } - func editingDidChange(sender: AnyObject) { + func editingDidChange(_ sender: AnyObject) { actualText = text; } - override func editingDidFinish(sender: AnyObject) { + override func editingDidFinish(_ sender: AnyObject) { super.editingDidFinish(sender) - secureTextEntry = false + isSecureTextEntry = false actualText = text; text = dotPlaceholder(); } @@ -129,9 +129,9 @@ class SecureTextField: TextField { var index = 0; let dots = NSMutableString(); while (index < text.characters.count) { - dots.appendString("•"); + dots.append("•"); index += 1; } return dots as String; } -} \ No newline at end of file +} diff --git a/Kiosk/Auction Listings/ListingsCountdownManager.swift b/Kiosk/Auction Listings/ListingsCountdownManager.swift index af991734..e5862060 100644 --- a/Kiosk/Auction Listings/ListingsCountdownManager.swift +++ b/Kiosk/Auction Listings/ListingsCountdownManager.swift @@ -5,7 +5,7 @@ class ListingsCountdownManager: NSObject { @IBOutlet weak var countdownLabel: UILabel! @IBOutlet weak var countdownContainerView: UIView! - let formatter = NSNumberFormatter() + let formatter = NumberFormatter() let sale = Variable(nil) @@ -23,7 +23,7 @@ class ListingsCountdownManager: NSObject { } } - private var _timer: NSTimer? = nil + fileprivate var _timer: Timer? = nil override func awakeFromNib() { super.awakeFromNib() @@ -38,17 +38,17 @@ class ListingsCountdownManager: NSObject { func setFonts() { (countdownContainerView.subviews).forEach{ (view) -> () in if let label = view as? UILabel { - label.font = UIFont.serifFontWithSize(15) + label.font = UIFont.serifFont(withSize: 15) } } - countdownLabel.font = UIFont.sansSerifFontWithSize(20) + countdownLabel.font = UIFont.sansSerifFont(withSize: 20) } - func setLabelsHidden(hidden: Bool) { - countdownContainerView.hidden = hidden + func setLabelsHidden(_ hidden: Bool) { + countdownContainerView.isHidden = hidden } - func setLabelsHiddenIfSynced(hidden: Bool) { + func setLabelsHiddenIfSynced(_ hidden: Bool) { if time.inSync() { setLabelsHidden(hidden) } @@ -56,27 +56,27 @@ class ListingsCountdownManager: NSObject { func hideDenomenatorLabels() { for subview in countdownContainerView.subviews { - subview.hidden = subview != countdownLabel + subview.isHidden = subview != countdownLabel } } func startTimer() { - let timer = NSTimer(timeInterval: 0.49, target: self, selector: #selector(ListingsCountdownManager.tick(_:)), userInfo: nil, repeats: true) - NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes) + let timer = Timer(timeInterval: 0.49, target: self, selector: #selector(ListingsCountdownManager.tick(_:)), userInfo: nil, repeats: true) + RunLoop.main.add(timer, forMode: RunLoopMode.commonModes) _timer = timer self.tick(timer) } - func tick(timer: NSTimer) { + func tick(_ timer: Timer) { guard let sale = sale.value else { return } guard time.inSync() else { return } guard sale.id != "" else { return } if sale.isActive(time) { let now = time.date() - let components = NSCalendar.currentCalendar().components([.Hour, .Minute, .Second], fromDate: now, toDate: sale.endDate, options: []) + let components = Calendar.currentCalendar().components([.Hour, .Minute, .Second], fromDate: now, toDate: sale.endDate, options: []) self.countdownLabel.text = "\(formatter.stringFromNumber(components.hour)!) : \(formatter.stringFromNumber(components.minute)!) : \(formatter.stringFromNumber(components.second)!)" diff --git a/Kiosk/Auction Listings/ListingsViewController.swift b/Kiosk/Auction Listings/ListingsViewController.swift index 057de032..0cabddea 100644 --- a/Kiosk/Auction Listings/ListingsViewController.swift +++ b/Kiosk/Auction Listings/ListingsViewController.swift @@ -15,7 +15,7 @@ class ListingsViewController: UIViewController { var downloadImage: ListingsCollectionViewCell.DownloadImageClosure = { (url, imageView) -> () in if let url = url { - imageView.sd_setImageWithURL(url) + imageView.sd_setImage(with: url as URL!) } else { imageView.image = nil } @@ -52,7 +52,7 @@ class ListingsViewController: UIViewController { // Set up development environment. if AppSetup.sharedState.isTesting { - stagingFlag.hidden = true + stagingFlag.isHidden = true } else { if APIKeys.sharedKeys.stubResponses { stagingFlag.image = UIImage(named: "StubbingFlag") @@ -60,7 +60,7 @@ class ListingsViewController: UIViewController { let flagImageName = AppSetup.sharedState.useStaging ? "StagingFlag" : "ProductionFlag" stagingFlag.image = UIImage(named: flagImageName) } else { - stagingFlag.hidden = AppSetup.sharedState.useStaging == false + stagingFlag.isHidden = AppSetup.sharedState.useStaging == false } } @@ -126,29 +126,29 @@ class ListingsViewController: UIViewController { .addDisposableTo(rx_disposeBag) } - override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue == .ShowSaleArtworkDetails { let saleArtwork = sender as! SaleArtwork! - let detailsViewController = segue.destinationViewController as! SaleArtworkDetailsViewController + let detailsViewController = segue.destination as! SaleArtworkDetailsViewController detailsViewController.saleArtwork = saleArtwork detailsViewController.provider = provider - ARAnalytics.event("Show Artwork Details", withProperties: ["id": saleArtwork.artwork.id]) + ARAnalytics.event("Show Artwork Details", withProperties: ["id": saleArtwork?.artwork.id]) } } - override func viewWillAppear(animated: Bool) { + override func viewWillAppear(_ animated: Bool) { let switchHeightPredicate = "\(switchView.intrinsicContentSize().height)" switchView.constrainHeight(switchHeightPredicate) - switchView.alignTop("\(64+VerticalMargins)", leading: "\(HorizontalMargins)", bottom: nil, trailing: "-\(HorizontalMargins)", toView: view) - collectionView.constrainTopSpaceToView(switchView, predicate: "0") - collectionView.alignTop(nil, leading: "0", bottom: "0", trailing: "0", toView: view) + switchView.alignTop("\(64+VerticalMargins)", leading: "\(HorizontalMargins)", bottom: nil, trailing: "-\(HorizontalMargins)", to: view) + collectionView.constrainTopSpace(to: switchView, predicate: "0") + collectionView.alignTop(nil, leading: "0", bottom: "0", trailing: "0", to: view) collectionView.contentInset = UIEdgeInsetsMake(40, 0, 80, 0) } } extension ListingsViewController { - class func instantiateFromStoryboard(storyboard: UIStoryboard) -> ListingsViewController { + class func instantiateFromStoryboard(_ storyboard: UIStoryboard) -> ListingsViewController { return storyboard.viewControllerWithID(.AuctionListings) as! ListingsViewController } } @@ -157,11 +157,11 @@ extension ListingsViewController { extension ListingsViewController: UICollectionViewDataSource, UICollectionViewDelegate, ARCollectionViewMasonryLayoutDelegate { - func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return viewModel.numberOfSaleArtworks } - func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellIdentifier.value, forIndexPath: indexPath) if let listingsCell = cell as? ListingsCollectionViewCell { @@ -190,7 +190,7 @@ extension ListingsViewController: UICollectionViewDataSource, UICollectionViewDe return cell } - func collectionView(collectionView: UICollectionView!, layout collectionViewLayout: ARCollectionViewMasonryLayout!, variableDimensionForItemAtIndexPath indexPath: NSIndexPath!) -> CGFloat { + func collectionView(_ collectionView: UICollectionView!, layout collectionViewLayout: ARCollectionViewMasonryLayout!, variableDimensionForItemAt indexPath: IndexPath!) -> CGFloat { return MasonryCollectionViewCell.heightForCellWithImageAspectRatio(viewModel.imageAspectRatioForSaleArtworkAtIndexPath(indexPath)) } } @@ -199,32 +199,32 @@ extension ListingsViewController: UICollectionViewDataSource, UICollectionViewDe private extension ListingsViewController { - func showDetailsForSaleArtwork(saleArtwork: SaleArtwork) { + func showDetails(forSaleArtwork saleArtwork: SaleArtwork) { ARAnalytics.event("Artwork Details Tapped", withProperties: ["id": saleArtwork.artwork.id]) - performSegueWithIdentifier(SegueIdentifier.ShowSaleArtworkDetails.rawValue, sender: saleArtwork) + self.performSegue(withIdentifier: SegueIdentifier.ShowSaleArtworkDetails.rawValue, sender: saleArtwork) } - func presentModalForSaleArtwork(saleArtwork: SaleArtwork) { + func presentModalForSaleArtwork(_ saleArtwork: SaleArtwork) { bid(viewModel.auctionID, saleArtwork: saleArtwork, allowAnimations: self.allowAnimations, provider: provider) } // MARK: Class methods class func masonryLayout() -> ARCollectionViewMasonryLayout { - let layout = ARCollectionViewMasonryLayout(direction: .Vertical) - layout.itemMargins = CGSizeMake(65, 20) - layout.dimensionLength = CGFloat(MasonryCollectionViewCellWidth) - layout.rank = 3 - layout.contentInset = UIEdgeInsetsMake(0.0, 0.0, CGFloat(VerticalMargins), 0.0) + let layout = ARCollectionViewMasonryLayout(direction: .vertical) + layout?.itemMargins = CGSize(width: 65, height: 20) + layout?.dimensionLength = CGFloat(MasonryCollectionViewCellWidth) + layout?.rank = 3 + layout?.contentInset = UIEdgeInsetsMake(0.0, 0.0, CGFloat(VerticalMargins), 0.0) - return layout + return layout! } class func tableLayout(width: CGFloat) -> UICollectionViewFlowLayout { let layout = UICollectionViewFlowLayout() TableCollectionViewCell.Width = width - layout.itemSize = CGSizeMake(width, TableCollectionViewCell.Height) + layout.itemSize = CGSize(width: width, height: TableCollectionViewCell.Height) layout.minimumLineSpacing = 0.0 return layout @@ -235,14 +235,14 @@ private extension ListingsViewController { extension UICollectionView { - class func listingsCollectionViewWithDelegateDatasource(delegateDatasource: ListingsViewController) -> UICollectionView { - let collectionView = UICollectionView(frame: CGRectZero, collectionViewLayout: ListingsViewController.masonryLayout()) - collectionView.backgroundColor = .clearColor() + class func listingsCollectionViewWithDelegateDatasource(_ delegateDatasource: ListingsViewController) -> UICollectionView { + let collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: ListingsViewController.masonryLayout()) + collectionView.backgroundColor = .clear() collectionView.dataSource = delegateDatasource collectionView.delegate = delegateDatasource collectionView.alwaysBounceVertical = true - collectionView.registerClass(MasonryCollectionViewCell.self, forCellWithReuseIdentifier: MasonryCellIdentifier) - collectionView.registerClass(TableCollectionViewCell.self, forCellWithReuseIdentifier: TableCellIdentifier) + collectionView.register(MasonryCollectionViewCell.self, forCellWithReuseIdentifier: MasonryCellIdentifier) + collectionView.register(TableCollectionViewCell.self, forCellWithReuseIdentifier: TableCellIdentifier) collectionView.allowsSelection = false return collectionView } diff --git a/Kiosk/Auction Listings/ListingsViewModel.swift b/Kiosk/Auction Listings/ListingsViewModel.swift index 453b6b45..52bdb882 100644 --- a/Kiosk/Auction Listings/ListingsViewModel.swift +++ b/Kiosk/Auction Listings/ListingsViewModel.swift @@ -7,22 +7,22 @@ typealias PresentModalClosure = (SaleArtwork) -> Void protocol ListingsViewModelType { var auctionID: String { get } - var syncInterval: NSTimeInterval { get } + var syncInterval: TimeInterval { get } var pageSize: Int { get } - var logSync: (NSDate) -> Void { get } + var logSync: (Date) -> Void { get } var numberOfSaleArtworks: Int { get } var showSpinner: Observable! { get } var gridSelected: Observable! { get } var updatedContents: Observable { get } - var scheduleOnBackground: (observable: Observable) -> Observable { get } - var scheduleOnForeground: (observable: Observable<[SaleArtwork]>) -> Observable<[SaleArtwork]> { get } + var scheduleOnBackground: (_ observable: Observable) -> Observable { get } + var scheduleOnForeground: (_ observable: Observable<[SaleArtwork]>) -> Observable<[SaleArtwork]> { get } - func saleArtworkViewModelAtIndexPath(indexPath: NSIndexPath) -> SaleArtworkViewModel - func showDetailsForSaleArtworkAtIndexPath(indexPath: NSIndexPath) - func presentModalForSaleArtworkAtIndexPath(indexPath: NSIndexPath) - func imageAspectRatioForSaleArtworkAtIndexPath(indexPath: NSIndexPath) -> CGFloat? + func saleArtworkViewModelAtIndexPath(_ indexPath: IndexPath) -> SaleArtworkViewModel + func showDetailsForSaleArtworkAtIndexPath(_ indexPath: IndexPath) + func presentModalForSaleArtworkAtIndexPath(_ indexPath: IndexPath) + func imageAspectRatioForSaleArtworkAtIndexPath(_ indexPath: IndexPath) -> CGFloat? } // Cheating here, should be in the instance but there's only ever one instance, so ¯\_(ツ)_/¯ @@ -31,15 +31,15 @@ private let backgroundScheduler = SerialDispatchQueueScheduler(globalConcurrentQ class ListingsViewModel: NSObject, ListingsViewModelType { // These are private to the view model – should not be accessed directly - private var saleArtworks = Variable(Array()) - private var sortedSaleArtworks = Variable>([]) + fileprivate var saleArtworks = Variable(Array()) + fileprivate var sortedSaleArtworks = Variable>([]) let auctionID: String let pageSize: Int - let syncInterval: NSTimeInterval - let logSync: (NSDate) -> Void - var scheduleOnBackground: (observable: Observable) -> Observable - var scheduleOnForeground: (observable: Observable<[SaleArtwork]>) -> Observable<[SaleArtwork]> + let syncInterval: TimeInterval + let logSync: (Date) -> Void + var scheduleOnBackground: (_ observable: Observable) -> Observable + var scheduleOnForeground: (_ observable: Observable<[SaleArtwork]>) -> Observable<[SaleArtwork]> var numberOfSaleArtworks: Int { return sortedSaleArtworks.value.count @@ -64,10 +64,10 @@ class ListingsViewModel: NSObject, ListingsViewModelType { showDetails: ShowDetailsClosure, presentModal: PresentModalClosure, pageSize: Int = 10, - syncInterval: NSTimeInterval = SyncInterval, - logSync:(NSDate) -> Void = ListingsViewModel.DefaultLogging, - scheduleOnBackground: (observable: Observable) -> Observable = ListingsViewModel.DefaultScheduler(onBackground: true), - scheduleOnForeground: (observable: Observable<[SaleArtwork]>) -> Observable<[SaleArtwork]> = ListingsViewModel.DefaultScheduler(onBackground: false), + syncInterval: TimeInterval = SyncInterval, + logSync:(Date) -> Void = ListingsViewModel.DefaultLogging, + scheduleOnBackground: (_ observable: Observable) -> Observable = ListingsViewModel.DefaultScheduler(onBackground: true), + scheduleOnForeground: (_ observable: Observable<[SaleArtwork]>) -> Observable<[SaleArtwork]> = ListingsViewModel.DefaultScheduler(onBackground: false), auctionID: String = AppSetup.sharedState.auctionID) { self.provider = provider @@ -87,7 +87,7 @@ class ListingsViewModel: NSObject, ListingsViewModelType { // MARK: Private Methods - private func setup(selectedIndex: Observable) { + fileprivate func setup(_ selectedIndex: Observable) { recurringListingsRequest() .takeUntil(rx_deallocated) @@ -126,12 +126,12 @@ class ListingsViewModel: NSObject, ListingsViewModelType { } - private func listingsRequestForPage(page: Int) -> Observable { + fileprivate func listingsRequest(forPage page: Int) -> Observable { return provider.request(.AuctionListings(id: auctionID, page: page, pageSize: self.pageSize)).filterSuccessfulStatusCodes().mapJSON() } // Repeatedly calls itself with page+1 until the count of the returned array is < pageSize. - private func retrieveAllListingsRequest(page: Int) -> Observable { + fileprivate func retrieveAllListingsRequest(_ page: Int) -> Observable { return Observable.create { [weak self] observer in guard let me = self else { return NopDisposable.instance } @@ -157,7 +157,7 @@ class ListingsViewModel: NSObject, ListingsViewModelType { } // Fetches all pages of the auction - private func allListingsRequest() -> Observable<[SaleArtwork]> { + fileprivate func allListingsRequest() -> Observable<[SaleArtwork]> { let backgroundJSONParsing = scheduleOnBackground(observable: retrieveAllListingsRequest(1)).reduce([AnyObject]()) { (memo, object) in guard let array = object as? Array else { return memo } @@ -170,10 +170,10 @@ class ListingsViewModel: NSObject, ListingsViewModelType { return scheduleOnForeground(observable: backgroundJSONParsing) } - private func recurringListingsRequest() -> Observable> { + fileprivate func recurringListingsRequest() -> Observable> { let recurring = Observable.interval(syncInterval, scheduler: MainScheduler.instance) - .map { _ in NSDate() } - .startWith(NSDate()) + .map { _ in Date() } + .startWith(Date()) .takeUntil(rx_deallocating) @@ -202,13 +202,13 @@ class ListingsViewModel: NSObject, ListingsViewModelType { // MARK: Private class methods - private class func DefaultLogging(date: NSDate) { + fileprivate class func DefaultLogging(_ date: Date) { #if (arch(i386) || arch(x86_64)) && os(iOS) logger.log("Syncing on \(date)") #endif } - private class func DefaultScheduler(onBackground background: Bool) -> (observable: Observable) -> Observable { + fileprivate class func DefaultScheduler(onBackground background: Bool) -> (_ observable: Observable) -> Observable { return { observable in if background { return observable.observeOn(backgroundScheduler) @@ -220,72 +220,72 @@ class ListingsViewModel: NSObject, ListingsViewModelType { // MARK: Public methods - func saleArtworkViewModelAtIndexPath(indexPath: NSIndexPath) -> SaleArtworkViewModel { + func saleArtworkViewModel(atIndexPath indexPath: IndexPath) -> SaleArtworkViewModel { return sortedSaleArtworks.value[indexPath.item].viewModel } - func imageAspectRatioForSaleArtworkAtIndexPath(indexPath: NSIndexPath) -> CGFloat? { + func imageAspectRatioForSaleArtwork(atIndexPath indexPath: IndexPath) -> CGFloat? { return sortedSaleArtworks.value[indexPath.item].artwork.defaultImage?.aspectRatio } - func showDetailsForSaleArtworkAtIndexPath(indexPath: NSIndexPath) { + func showDetailsForSaleArtwork(atIndexPath indexPath: IndexPath) { showDetails(sortedSaleArtworks.value[indexPath.item]) } - func presentModalForSaleArtworkAtIndexPath(indexPath: NSIndexPath) { + func presentModalForSaleArtwork(atIndexPath indexPath: IndexPath) { presentModal(sortedSaleArtworks.value[indexPath.item]) } // MARK: - Switch Values enum SwitchValues: Int { - case Grid = 0 - case LeastBids - case MostBids - case HighestCurrentBid - case LowestCurrentBid - case Alphabetical + case grid = 0 + case leastBids + case mostBids + case highestCurrentBid + case lowestCurrentBid + case alphabetical var name: String { switch self { - case .Grid: + case .grid: return "Grid" - case .LeastBids: + case .leastBids: return "Least Bids" - case .MostBids: + case .mostBids: return "Most Bids" - case .HighestCurrentBid: + case .highestCurrentBid: return "Highest Bid" - case .LowestCurrentBid: + case .lowestCurrentBid: return "Lowest Bid" - case .Alphabetical: + case .alphabetical: return "A–Z" } } - func sortSaleArtworks(saleArtworks: [SaleArtwork]) -> [SaleArtwork] { + func sortSaleArtworks(_ saleArtworks: [SaleArtwork]) -> [SaleArtwork] { switch self { - case Grid: + case .grid: return saleArtworks - case LeastBids: - return saleArtworks.sort(leastBidsSort) - case MostBids: - return saleArtworks.sort(mostBidsSort) - case HighestCurrentBid: - return saleArtworks.sort(highestCurrentBidSort) - case LowestCurrentBid: - return saleArtworks.sort(lowestCurrentBidSort) - case Alphabetical: - return saleArtworks.sort(alphabeticalSort) + case .leastBids: + return saleArtworks.sorted(by: leastBidsSort) + case .mostBids: + return saleArtworks.sorted(by: mostBidsSort) + case .highestCurrentBid: + return saleArtworks.sorted(by: highestCurrentBidSort) + case .lowestCurrentBid: + return saleArtworks.sorted(by: lowestCurrentBidSort) + case .alphabetical: + return saleArtworks.sorted(by: alphabeticalSort) } } static func allSwitchValues() -> [SwitchValues] { - return [Grid, LeastBids, MostBids, HighestCurrentBid, LowestCurrentBid, Alphabetical] + return [grid, leastBids, mostBids, highestCurrentBid, lowestCurrentBid, alphabetical] } static func allSwitchValueNames() -> [String] { - return allSwitchValues().map{$0.name.uppercaseString} + return allSwitchValues().map{$0.name.uppercased()} } } } @@ -308,31 +308,31 @@ extension Optional where Wrapped: IntOrZeroable { } } -func leastBidsSort(lhs: SaleArtwork, _ rhs: SaleArtwork) -> Bool { +func leastBidsSort(_ lhs: SaleArtwork, _ rhs: SaleArtwork) -> Bool { return (lhs.bidCount.intOrZero) < (rhs.bidCount.intOrZero) } -func mostBidsSort(lhs: SaleArtwork, _ rhs: SaleArtwork) -> Bool { +func mostBidsSort(_ lhs: SaleArtwork, _ rhs: SaleArtwork) -> Bool { return !leastBidsSort(lhs, rhs) } -func lowestCurrentBidSort(lhs: SaleArtwork, _ rhs: SaleArtwork) -> Bool { +func lowestCurrentBidSort(_ lhs: SaleArtwork, _ rhs: SaleArtwork) -> Bool { return (lhs.highestBidCents.intOrZero) < (rhs.highestBidCents.intOrZero) } -func highestCurrentBidSort(lhs: SaleArtwork, _ rhs: SaleArtwork) -> Bool { +func highestCurrentBidSort(_ lhs: SaleArtwork, _ rhs: SaleArtwork) -> Bool { return !lowestCurrentBidSort(lhs, rhs) } -func alphabeticalSort(lhs: SaleArtwork, _ rhs: SaleArtwork) -> Bool { - return lhs.artwork.sortableArtistID().caseInsensitiveCompare(rhs.artwork.sortableArtistID()) == .OrderedAscending +func alphabeticalSort(_ lhs: SaleArtwork, _ rhs: SaleArtwork) -> Bool { + return lhs.artwork.sortableArtistID().caseInsensitiveCompare(rhs.artwork.sortableArtistID()) == .orderedAscending } -func sortById(lhs: SaleArtwork, _ rhs: SaleArtwork) -> Bool { - return lhs.id.caseInsensitiveCompare(rhs.id) == .OrderedAscending +func sortById(_ lhs: SaleArtwork, _ rhs: SaleArtwork) -> Bool { + return lhs.id.caseInsensitiveCompare(rhs.id) == .orderedAscending } -private func update(currentSaleArtworks: [SaleArtwork], newSaleArtworks: [SaleArtwork]) -> Bool { +private func update(_ currentSaleArtworks: [SaleArtwork], newSaleArtworks: [SaleArtwork]) -> Bool { assert(currentSaleArtworks.count == newSaleArtworks.count, "Arrays' counts must be equal.") // Updating the currentSaleArtworks is easy. Both are already sorted as they came from the API (by lot #). // Because we assume that their length is the same, we just do a linear scan through and diff --git a/Kiosk/Auction Listings/MasonryCollectionViewCell.swift b/Kiosk/Auction Listings/MasonryCollectionViewCell.swift index 31a2375d..6b3fa645 100644 --- a/Kiosk/Auction Listings/MasonryCollectionViewCell.swift +++ b/Kiosk/Auction Listings/MasonryCollectionViewCell.swift @@ -3,22 +3,22 @@ import UIKit let MasonryCollectionViewCellWidth: CGFloat = 254 class MasonryCollectionViewCell: ListingsCollectionViewCell { - private lazy var bidView: UIView = { + fileprivate lazy var bidView: UIView = { let view = UIView() for subview in [self.currentBidLabel, self.numberOfBidsLabel] { view.addSubview(subview) - subview.alignTopEdgeWithView(view, predicate:"13") - subview.alignBottomEdgeWithView(view, predicate:"0") + subview.alignTopEdge(with: view, predicate:"13") + subview.alignBottomEdge(with: view, predicate:"0") subview.constrainHeight("18") } - self.currentBidLabel.alignLeadingEdgeWithView(view, predicate: "0") - self.numberOfBidsLabel.alignTrailingEdgeWithView(view, predicate: "0") + self.currentBidLabel.alignLeadingEdge(with: view, predicate: "0") + self.numberOfBidsLabel.alignTrailingEdge(with: view, predicate: "0") return view }() - private lazy var cellSubviews: [UIView] = [self.artworkImageView, self.lotNumberLabel, self.artistNameLabel, self.artworkTitleLabel, self.estimateLabel, self.bidView, self.bidButton, self.moreInfoLabel] + fileprivate lazy var cellSubviews: [UIView] = [self.artworkImageView, self.lotNumberLabel, self.artistNameLabel, self.artworkTitleLabel, self.estimateLabel, self.bidView, self.bidButton, self.moreInfoLabel] - private var artworkImageViewHeightConstraint: NSLayoutConstraint? + fileprivate var artworkImageViewHeightConstraint: NSLayoutConstraint? override func setup() { super.setup() @@ -28,23 +28,23 @@ class MasonryCollectionViewCell: ListingsCollectionViewCell { // Add subviews for subview in cellSubviews { self.contentView.addSubview(subview) - subview.alignLeading("0", trailing: "0", toView: self.contentView) + subview.alignLeading("0", trailing: "0", to: self.contentView) } // Constrain subviews - artworkImageView.alignTop("0", bottom: nil, toView: contentView) - let lotNumberTopConstraint = lotNumberLabel.alignAttribute(.Top, toAttribute: .Bottom, ofView: artworkImageView, predicate: "20").first as! NSLayoutConstraint - let artistNameTopConstraint = artistNameLabel.alignAttribute(.Top, toAttribute: .Bottom, ofView: lotNumberLabel, predicate: "10").first as! NSLayoutConstraint + artworkImageView.alignTop("0", bottom: nil, to: contentView) + let lotNumberTopConstraint = lotNumberLabel.alignAttribute(.top, to: .bottom, of: artworkImageView, predicate: "20").first as! NSLayoutConstraint + let artistNameTopConstraint = artistNameLabel.alignAttribute(.top, to: .bottom, of: lotNumberLabel, predicate: "10").first as! NSLayoutConstraint artistNameLabel.constrainHeight("20") - artworkTitleLabel.alignAttribute(.Top, toAttribute: .Bottom, ofView: artistNameLabel, predicate: "10") + artworkTitleLabel.alignAttribute(.top, to: .bottom, of: artistNameLabel, predicate: "10") artworkTitleLabel.constrainHeight("16") - estimateLabel.alignAttribute(.Top, toAttribute: .Bottom, ofView: artworkTitleLabel, predicate: "10") + estimateLabel.alignAttribute(.top, to: .bottom, of: artworkTitleLabel, predicate: "10") estimateLabel.constrainHeight("16") - bidView.alignAttribute(.Top, toAttribute: .Bottom, ofView: estimateLabel, predicate: "13") - bidButton.alignAttribute(.Top, toAttribute: .Bottom, ofView: currentBidLabel, predicate: "13") - moreInfoLabel.alignAttribute(.Top, toAttribute: .Bottom, ofView: bidButton, predicate: "0") + bidView.alignAttribute(.top, to: .bottom, of: estimateLabel, predicate: "13") + bidButton.alignAttribute(.top, to: .bottom, of: currentBidLabel, predicate: "13") + moreInfoLabel.alignAttribute(.top, to: .bottom, of: bidButton, predicate: "0") moreInfoLabel.constrainHeight("44") - moreInfoLabel.alignAttribute(.Bottom, toAttribute: .Bottom, ofView: contentView, predicate: "12") + moreInfoLabel.alignAttribute(.bottom, to: .bottom, of: contentView, predicate: "12") viewModel.flatMapTo(SaleArtworkViewModel.lotNumber) .subscribeNext { (lotNumber)in @@ -76,12 +76,12 @@ class MasonryCollectionViewCell: ListingsCollectionViewCell { override func layoutSubviews() { super.layoutSubviews() - bidView.drawTopDottedBorderWithColor(.artsyMediumGrey()) + bidView.drawTopDottedBorder(with: .artsyMediumGrey()) } } extension MasonryCollectionViewCell { - class func heightForCellWithImageAspectRatio(aspectRatio: CGFloat?) -> CGFloat { + class func heightForCellWithImageAspectRatio(_ aspectRatio: CGFloat?) -> CGFloat { let imageHeight = heightForImageWithAspectRatio(aspectRatio) let remainingHeight = 20 + // padding @@ -101,7 +101,7 @@ extension MasonryCollectionViewCell { } } -private func heightForImageWithAspectRatio(aspectRatio: CGFloat?) -> CGFloat { +private func heightForImage(withAspectRatio aspectRatio: CGFloat?) -> CGFloat { if let ratio = aspectRatio { if ratio != 0 { return CGFloat(MasonryCollectionViewCellWidth) / ratio diff --git a/Kiosk/Auction Listings/TableCollectionViewCell.swift b/Kiosk/Auction Listings/TableCollectionViewCell.swift index 636d8407..75c75f68 100644 --- a/Kiosk/Auction Listings/TableCollectionViewCell.swift +++ b/Kiosk/Auction Listings/TableCollectionViewCell.swift @@ -2,23 +2,23 @@ import UIKit import RxCocoa class TableCollectionViewCell: ListingsCollectionViewCell { - private lazy var infoView: UIView = { + fileprivate lazy var infoView: UIView = { let view = UIView() view.addSubview(self.lotNumberLabel) view.addSubview(self.artistNameLabel) view.addSubview(self.artworkTitleLabel) - self.lotNumberLabel.alignTop("0", bottom: nil, toView: view) - self.lotNumberLabel.alignLeading("0", trailing: "0", toView: view) - self.artistNameLabel.alignAttribute(.Top, toAttribute: .Bottom, ofView: self.lotNumberLabel, predicate: "5") - self.artistNameLabel.alignLeading("0", trailing: "0", toView: view) - self.artworkTitleLabel.alignLeading("0", trailing: "0", toView: view) - self.artworkTitleLabel.alignAttribute(.Top, toAttribute: .Bottom, ofView: self.artistNameLabel, predicate: "0") - self.artworkTitleLabel.alignTop(nil, bottom: "0", toView: view) + self.lotNumberLabel.alignTop("0", bottom: nil, to: view) + self.lotNumberLabel.alignLeading("0", trailing: "0", to: view) + self.artistNameLabel.alignAttribute(.top, to: .bottom, of: self.lotNumberLabel, predicate: "5") + self.artistNameLabel.alignLeading("0", trailing: "0", to: view) + self.artworkTitleLabel.alignLeading("0", trailing: "0", to: view) + self.artworkTitleLabel.alignAttribute(.top, to: .bottom, of: self.artistNameLabel, predicate: "0") + self.artworkTitleLabel.alignTop(nil, bottom: "0", to: view) return view }() - private lazy var cellSubviews: [UIView] = [self.artworkImageView, self.infoView, self.currentBidLabel, self.numberOfBidsLabel, self.bidButton] + fileprivate lazy var cellSubviews: [UIView] = [self.artworkImageView, self.infoView, self.currentBidLabel, self.numberOfBidsLabel, self.bidButton] override func setup() { super.setup() @@ -26,50 +26,50 @@ class TableCollectionViewCell: ListingsCollectionViewCell { contentView.constrainWidth("\(TableCollectionViewCell.Width)") // Configure subviews - numberOfBidsLabel.textAlignment = .Center - artworkImageView.contentMode = .ScaleAspectFill + numberOfBidsLabel.textAlignment = .center + artworkImageView.contentMode = .scaleAspectFill artworkImageView.clipsToBounds = true // Add subviews cellSubviews.forEach{ self.contentView.addSubview($0) } // Constrain subviews - artworkImageView.alignAttribute(.Width, toAttribute: .Height, ofView: artworkImageView, predicate: nil) - artworkImageView.alignTop("14", leading: "0", bottom: "-14", trailing: nil, toView: contentView) + artworkImageView.alignAttribute(.width, to: .height, of: artworkImageView, predicate: nil) + artworkImageView.alignTop("14", leading: "0", bottom: "-14", trailing: nil, to: contentView) artworkImageView.constrainHeight("56") - infoView.alignAttribute(.Left, toAttribute: .Right, ofView: artworkImageView, predicate: "28") - infoView.alignCenterYWithView(artworkImageView, predicate: "0") + infoView.alignAttribute(.left, to: .right, of: artworkImageView, predicate: "28") + infoView.alignCenterY(with: artworkImageView, predicate: "0") infoView.constrainWidth("300") - currentBidLabel.alignAttribute(.Left, toAttribute: .Right, ofView: infoView, predicate: "33") - currentBidLabel.alignCenterYWithView(artworkImageView, predicate: "0") + currentBidLabel.alignAttribute(.left, to: .right, of: infoView, predicate: "33") + currentBidLabel.alignCenterY(with: artworkImageView, predicate: "0") currentBidLabel.constrainWidth("180") - numberOfBidsLabel.alignAttribute(.Left, toAttribute: .Right, ofView: currentBidLabel, predicate: "33") - numberOfBidsLabel.alignCenterYWithView(artworkImageView, predicate: "0") - numberOfBidsLabel.alignAttribute(.Right, toAttribute: .Left, ofView: bidButton, predicate: "-33") + numberOfBidsLabel.alignAttribute(.left, to: .right, of: currentBidLabel, predicate: "33") + numberOfBidsLabel.alignCenterY(with: artworkImageView, predicate: "0") + numberOfBidsLabel.alignAttribute(.right, to: .left, of: bidButton, predicate: "-33") - bidButton.alignBottom(nil, trailing: "0", toView: contentView) - bidButton.alignCenterYWithView(artworkImageView, predicate: "0") + bidButton.alignBottom(nil, trailing: "0", to: contentView) + bidButton.alignCenterY(with: artworkImageView, predicate: "0") bidButton.constrainWidth("127") // Replaces the observable defined in the superclass, normally used to emit taps to a "More Info" label, which we don't have. let recognizer = UITapGestureRecognizer() contentView.addGestureRecognizer(recognizer) - self.moreInfo = recognizer.rx_event.map { _ -> NSDate in - return NSDate() + self.moreInfo = recognizer.rx_event.map { _ -> Date in + return Date() } } override func layoutSubviews() { super.layoutSubviews() - contentView.drawBottomSolidBorderWithColor(.artsyMediumGrey()) + contentView.drawBottomSolidBorder(with: .artsyMediumGrey()) } } extension TableCollectionViewCell { - private struct SharedDimensions { + fileprivate struct SharedDimensions { var width: CGFloat = 0 var height: CGFloat = 84 diff --git a/Kiosk/Auction Listings/WebViewController.swift b/Kiosk/Auction Listings/WebViewController.swift index c04eaa8e..24d2a1c0 100644 --- a/Kiosk/Auction Listings/WebViewController.swift +++ b/Kiosk/Auction Listings/WebViewController.swift @@ -5,9 +5,9 @@ let modalHeight: CGFloat = 660 class WebViewController: DZNWebViewController { var showToolbar = true - convenience init(url: NSURL) { + convenience override init(url: URL) { self.init() - self.URL = url + self.url = url } override func viewDidLoad() { @@ -19,7 +19,7 @@ class WebViewController: DZNWebViewController { self.navigationItem.rightBarButtonItem = nil } - override func viewWillAppear(animated: Bool) { + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationController?.setNavigationBarHidden(true, animated:false) navigationController?.setToolbarHidden(!showToolbar, animated:false) @@ -34,28 +34,28 @@ class ModalWebViewController: WebViewController { closeButton = UIButton() view.addSubview(closeButton) - closeButton.titleLabel?.font = UIFont.sansSerifFontWithSize(14) - closeButton.setTitleColor(.artsyMediumGrey(), forState:.Normal) - closeButton.setTitle("CLOSE", forState:.Normal) + closeButton.titleLabel?.font = UIFont.sansSerifFont(withSize: 14) + closeButton.setTitleColor(.artsyMediumGrey(), for:UIControlState()) + closeButton.setTitle("CLOSE", for:UIControlState()) closeButton.constrainWidth("140", height: "72") - closeButton.alignTop("0", leading:"0", bottom:nil, trailing:nil, toView:view) - closeButton.addTarget(self, action:#selector(closeTapped(_:)), forControlEvents:.TouchUpInside) + closeButton.alignTop("0", leading:"0", bottom:nil, trailing:nil, to:view) + closeButton.addTarget(self, action:#selector(closeTapped(_:)), for:.touchUpInside) var height = modalHeight if let nav = navigationController { - if !nav.navigationBarHidden { height -= CGRectGetHeight(nav.navigationBar.frame) } - if !nav.toolbarHidden { height -= CGRectGetHeight(nav.toolbar.frame) } + if !nav.isNavigationBarHidden { height -= nav.navigationBar.frame.height } + if !nav.isToolbarHidden { height -= nav.toolbar.frame.height } } - preferredContentSize = CGSizeMake(815, height) + preferredContentSize = CGSize(width: 815, height: height) } - override func viewWillAppear(animated: Bool) { + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationController?.view.superview?.layer.cornerRadius = 0; } - func closeTapped(sender: AnyObject) { - presentingViewController?.dismissViewControllerAnimated(true, completion:nil) + func closeTapped(_ sender: AnyObject) { + presentingViewController?.dismiss(animated: true, completion:nil) } } diff --git a/Kiosk/Bid Fulfillment/AdminCCBypassNetworkModel.swift b/Kiosk/Bid Fulfillment/AdminCCBypassNetworkModel.swift index ecef70cf..cdce372e 100644 --- a/Kiosk/Bid Fulfillment/AdminCCBypassNetworkModel.swift +++ b/Kiosk/Bid Fulfillment/AdminCCBypassNetworkModel.swift @@ -3,13 +3,13 @@ import RxSwift import Moya enum BypassResult { - case RequireCC - case SkipCCRequirement + case requireCC + case skipCCRequirement } protocol AdminCCBypassNetworkModelType { - func checkForAdminCCBypass(saleID: String, authorizedNetworking: AuthorizedNetworking) -> Observable + func checkForAdminCCBypass(_ saleID: String, authorizedNetworking: AuthorizedNetworking) -> Observable } class AdminCCBypassNetworkModel: AdminCCBypassNetworkModelType { @@ -37,4 +37,4 @@ class AdminCCBypassNetworkModel: AdminCCBypassNetworkModelType { } .logError() } -} \ No newline at end of file +} diff --git a/Kiosk/Bid Fulfillment/BidCheckingNetworkModel.swift b/Kiosk/Bid Fulfillment/BidCheckingNetworkModel.swift index 1f6320eb..305bf954 100644 --- a/Kiosk/Bid Fulfillment/BidCheckingNetworkModel.swift +++ b/Kiosk/Bid Fulfillment/BidCheckingNetworkModel.swift @@ -6,7 +6,7 @@ enum BidCheckingError: String { case PollingExceeded } -extension BidCheckingError: ErrorType { } +extension BidCheckingError: Error { } protocol BidCheckingNetworkModelType { var bidDetails: BidDetails { get } @@ -20,9 +20,9 @@ protocol BidCheckingNetworkModelType { class BidCheckingNetworkModel: NSObject, BidCheckingNetworkModelType { - private var pollInterval = NSTimeInterval(1) - private var maxPollRequests = 20 - private var pollRequests = 0 + fileprivate var pollInterval = TimeInterval(1) + fileprivate var maxPollRequests = 20 + fileprivate var pollRequests = 0 // inputs let provider: Networking @@ -33,7 +33,7 @@ class BidCheckingNetworkModel: NSObject, BidCheckingNetworkModelType { var isHighestBidder = Variable(false) var reserveNotMet = Variable(false) - private var mostRecentSaleArtwork: SaleArtwork? + fileprivate var mostRecentSaleArtwork: SaleArtwork? init(provider: Networking, bidDetails: BidDetails) { self.provider = provider @@ -69,7 +69,7 @@ class BidCheckingNetworkModel: NSObject, BidCheckingNetworkModelType { }.catchErrorJustReturn() } - private func pollForUpdatedBidderPosition(bidderPositionId: String, provider: AuthorizedNetworking) -> Observable { + fileprivate func poll(forUpdatedBidderPosition bidderPositionId: String, provider: AuthorizedNetworking) -> Observable { let updatedBidderPosition = getUpdatedBidderPosition(bidderPositionId, provider: provider) .flatMap { bidderPositionObject -> Observable in self.pollRequests += 1 @@ -103,7 +103,7 @@ class BidCheckingNetworkModel: NSObject, BidCheckingNetworkModelType { .then { updatedBidderPosition } } - private func checkForMaxBid(provider: AuthorizedNetworking) -> Observable { + fileprivate func checkForMaxBid(provider: AuthorizedNetworking) -> Observable { return getMyBidderPositions(provider) .doOnNext{ newBidderPositions in @@ -116,11 +116,11 @@ class BidCheckingNetworkModel: NSObject, BidCheckingNetworkModelType { .map(void) } - private func getMyBidderPositions(provider: AuthorizedNetworking) -> Observable<[BidderPosition]> { + fileprivate func getMyBidderPositions(provider: AuthorizedNetworking) -> Observable<[BidderPosition]> { let artworkID = bidDetails.saleArtwork!.artwork.id; let auctionID = bidDetails.saleArtwork!.auctionID! - let endpoint = ArtsyAuthenticatedAPI.MyBidPositionsForAuctionArtwork(auctionID: auctionID, artworkID: artworkID) + let endpoint = ArtsyAuthenticatedAPI.myBidPositionsForAuctionArtwork(auctionID: auctionID, artworkID: artworkID) return provider .request(endpoint) .filterSuccessfulStatusCodes() @@ -128,12 +128,12 @@ class BidCheckingNetworkModel: NSObject, BidCheckingNetworkModelType { .mapToObjectArray(BidderPosition) } - private func getUpdatedSaleArtwork() -> Observable { + fileprivate func getUpdatedSaleArtwork() -> Observable { let artworkID = bidDetails.saleArtwork!.artwork.id; let auctionID = bidDetails.saleArtwork!.auctionID! - let endpoint: ArtsyAPI = ArtsyAPI.AuctionInfoForArtwork(auctionID: auctionID, artworkID: artworkID) + let endpoint: ArtsyAPI = ArtsyAPI.auctionInfoForArtwork(auctionID: auctionID, artworkID: artworkID) return provider .request(endpoint) .filterSuccessfulStatusCodes() @@ -141,7 +141,7 @@ class BidCheckingNetworkModel: NSObject, BidCheckingNetworkModelType { .mapToObject(SaleArtwork) } - private func getUpdatedBidderPosition(bidderPositionId: String, provider: AuthorizedNetworking) -> Observable { + fileprivate func getUpdatedBidderPosition(bidderPositionId: String, provider: AuthorizedNetworking) -> Observable { let endpoint = ArtsyAuthenticatedAPI.MyBidPosition(id: bidderPositionId) return provider .request(endpoint) diff --git a/Kiosk/Bid Fulfillment/BidDetailsPreviewView.swift b/Kiosk/Bid Fulfillment/BidDetailsPreviewView.swift index 334a5382..a91d5ed3 100644 --- a/Kiosk/Bid Fulfillment/BidDetailsPreviewView.swift +++ b/Kiosk/Bid Fulfillment/BidDetailsPreviewView.swift @@ -20,7 +20,7 @@ class BidDetailsPreviewView: UIView { dynamic let currentBidPriceLabel = ARSerifLabel() override func awakeFromNib() { - self.backgroundColor = .whiteColor() + self.backgroundColor = .white() artistNameLabel.numberOfLines = 1 artworkTitleLabel.numberOfLines = 1 @@ -28,10 +28,10 @@ class BidDetailsPreviewView: UIView { artworkImageView.alignRight = true artworkImageView.alignBottom = true - artworkImageView.contentMode = .ScaleAspectFit + artworkImageView.contentMode = .scaleAspectFit - artistNameLabel.font = UIFont.sansSerifFontWithSize(14) - currentBidPriceLabel.font = UIFont.serifBoldFontWithSize(14) + artistNameLabel.font = UIFont.sansSerifFont(withSize: 14) + currentBidPriceLabel.font = UIFont.serifBoldFont(withSize: 14) let artwork = _bidDetails .asObservable() @@ -91,19 +91,19 @@ class BidDetailsPreviewView: UIView { self.constrainHeight("60") - artworkImageView.alignTop("0", leading: "0", bottom: "0", trailing: nil, toView: self) + artworkImageView.alignTop("0", leading: "0", bottom: "0", trailing: nil, to: self) artworkImageView.constrainWidth("84") artworkImageView.constrainHeight("60") - artistNameLabel.alignAttribute(.Top, toAttribute: .Top, ofView: self, predicate: "0") + artistNameLabel.alignAttribute(.top, to: .top, of: self, predicate: "0") artistNameLabel.constrainHeight("16") - artworkTitleLabel.alignAttribute(.Top, toAttribute: .Bottom, ofView: artistNameLabel, predicate: "8") + artworkTitleLabel.alignAttribute(.top, to: .bottom, of: artistNameLabel, predicate: "8") artworkTitleLabel.constrainHeight("16") - currentBidPriceLabel.alignAttribute(.Top, toAttribute: .Bottom, ofView: artworkTitleLabel, predicate: "4") + currentBidPriceLabel.alignAttribute(.top, to: .bottom, of: artworkTitleLabel, predicate: "4") currentBidPriceLabel.constrainHeight("16") - UIView.alignAttribute(.Leading, ofViews: [artistNameLabel, artworkTitleLabel, currentBidPriceLabel], toAttribute:.Trailing, ofViews: [artworkImageView, artworkImageView, artworkImageView], predicate: "20") - UIView.alignAttribute(.Trailing, ofViews: [artistNameLabel, artworkTitleLabel, currentBidPriceLabel], toAttribute:.Trailing, ofViews: [self, self, self], predicate: "0") + UIView.alignAttribute(.leading, ofViews: [artistNameLabel, artworkTitleLabel, currentBidPriceLabel], to:.trailing, ofViews: [artworkImageView, artworkImageView, artworkImageView], predicate: "20") + UIView.alignAttribute(.trailing, ofViews: [artistNameLabel, artworkTitleLabel, currentBidPriceLabel], to:.trailing, ofViews: [self, self, self], predicate: "0") } diff --git a/Kiosk/Bid Fulfillment/BidderNetworkModel.swift b/Kiosk/Bid Fulfillment/BidderNetworkModel.swift index 0100b19a..01e193a5 100644 --- a/Kiosk/Bid Fulfillment/BidderNetworkModel.swift +++ b/Kiosk/Bid Fulfillment/BidderNetworkModel.swift @@ -41,7 +41,7 @@ private extension BidderNetworkModel { // MARK: - Chained observables - func checkUserEmailExists(email: String) -> Observable { + func checkUserEmailExists(_ email: String) -> Observable { let request = provider.request(.FindExistingEmailRegistration(email: email)) return request.map { response in @@ -152,7 +152,7 @@ private extension BidderNetworkModel { }.logServerError("Getting user bidders failed.") } - func registerToAuction(auctionID: String, provider: AuthorizedNetworking) -> Observable { + func register(toAuction auctionID: String, provider: AuthorizedNetworking) -> Observable { let endpoint = ArtsyAuthenticatedAPI.RegisterToBid(auctionID: auctionID) let register = provider.request(endpoint) .filterSuccessfulStatusCodes() @@ -183,7 +183,7 @@ private extension BidderNetworkModel { } func getMyPaddleNumber(provider: AuthorizedNetworking) -> Observable { - let endpoint = ArtsyAuthenticatedAPI.Me + let endpoint = ArtsyAuthenticatedAPI.me return provider.request(endpoint) .filterSuccessfulStatusCodes() .mapJSON() diff --git a/Kiosk/Bid Fulfillment/ConfirmYourBidArtsyLoginViewController.swift b/Kiosk/Bid Fulfillment/ConfirmYourBidArtsyLoginViewController.swift index 94b4ee25..b9aa8f03 100644 --- a/Kiosk/Bid Fulfillment/ConfirmYourBidArtsyLoginViewController.swift +++ b/Kiosk/Bid Fulfillment/ConfirmYourBidArtsyLoginViewController.swift @@ -11,7 +11,7 @@ class ConfirmYourBidArtsyLoginViewController: UIViewController { @IBOutlet var useArtsyBidderButton: UIButton! @IBOutlet var confirmCredentialsButton: Button! - private let _viewWillDisappear = PublishSubject() + fileprivate let _viewWillDisappear = PublishSubject() var viewWillDisappear: Observable { return self._viewWillDisappear.asObserver() } @@ -19,18 +19,18 @@ class ConfirmYourBidArtsyLoginViewController: UIViewController { var createNewAccount = false var provider: Networking! - class func instantiateFromStoryboard(storyboard: UIStoryboard) -> ConfirmYourBidArtsyLoginViewController { + class func instantiateFromStoryboard(_ storyboard: UIStoryboard) -> ConfirmYourBidArtsyLoginViewController { return storyboard.viewControllerWithID(.ConfirmYourBidArtsyLogin) as! ConfirmYourBidArtsyLoginViewController } override func viewDidLoad() { super.viewDidLoad() - let titleString = useArtsyBidderButton.titleForState(useArtsyBidderButton.state)! ?? "" - let attributes = [NSUnderlineStyleAttributeName: NSUnderlineStyle.StyleSingle.rawValue, - NSFontAttributeName: useArtsyBidderButton.titleLabel!.font]; + let titleString = useArtsyBidderButton.title(for: useArtsyBidderButton.state)! ?? "" + let attributes = [NSUnderlineStyleAttributeName: NSUnderlineStyle.styleSingle.rawValue, + NSFontAttributeName: useArtsyBidderButton.titleLabel!.font] as [String : Any]; let attrTitle = NSAttributedString(string: titleString, attributes:attributes) - useArtsyBidderButton.setAttributedTitle(attrTitle, forState:useArtsyBidderButton.state) + useArtsyBidderButton.setAttributedTitle(attrTitle, for:useArtsyBidderButton.state) let nav = self.fulfillmentNav() let bidDetails = nav.bidDetails @@ -86,19 +86,19 @@ class ConfirmYourBidArtsyLoginViewController: UIViewController { } } - override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { - super.prepareForSegue(segue, sender: sender) + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + super.prepare(for: segue, sender: sender) if segue == .EmailLoginConfirmedHighestBidder { - let viewController = segue.destinationViewController as! LoadingViewController + let viewController = segue.destination as! LoadingViewController viewController.provider = provider } else if segue == .ArtsyUserHasNotRegisteredCard { - let viewController = segue.destinationViewController as! RegisterViewController + let viewController = segue.destination as! RegisterViewController viewController.provider = provider } } - override func viewWillAppear(animated: Bool) { + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) if emailTextField.text.isNilOrEmpty { emailTextField.becomeFirstResponder() @@ -107,7 +107,7 @@ class ConfirmYourBidArtsyLoginViewController: UIViewController { } } - override func viewWillDisappear(animated: Bool) { + override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) _viewWillDisappear.onNext() } @@ -119,8 +119,8 @@ class ConfirmYourBidArtsyLoginViewController: UIViewController { passwordTextField.text = "" } - @IBAction func forgotPasswordTapped(sender: AnyObject) { - let alertController = UIAlertController(title: "Forgot Password", message: "Please enter your email address and we'll send you a reset link.", preferredStyle: .Alert) + @IBAction func forgotPasswordTapped(_ sender: AnyObject) { + let alertController = UIAlertController(title: "Forgot Password", message: "Please enter your email address and we'll send you a reset link.", preferredStyle: .alert) let submitAction = UIAlertAction.Action("Send", style: .Default) let email = Variable("") @@ -135,9 +135,9 @@ class ConfirmYourBidArtsyLoginViewController: UIViewController { .map(void) }) - let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel) { (_) in } + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (_) in } - alertController.addTextFieldWithConfigurationHandler { textField in + alertController.addTextField { textField in textField.placeholder = "email@domain.com" textField.text = self.emailTextField.text @@ -146,7 +146,7 @@ class ConfirmYourBidArtsyLoginViewController: UIViewController { .bindTo(email) .addDisposableTo(textField.rx_disposeBag) - NSNotificationCenter.defaultCenter().addObserverForName(UITextFieldTextDidChangeNotification, object: textField, queue: NSOperationQueue.mainQueue()) { (notification) in + NotificationCenter.defaultCenter().addObserverForName(NSNotification.Name.UITextFieldTextDidChange, object: textField, queue: OperationQueue.mainQueue()) { (notification) in submitAction.enabled = stringIsEmailAddress(textField.text ?? "").boolValue } } @@ -154,11 +154,11 @@ class ConfirmYourBidArtsyLoginViewController: UIViewController { alertController.addAction(submitAction) alertController.addAction(cancelAction) - self.presentViewController(alertController, animated: true) {} + self.present(alertController, animated: true) {} } - func creditCard(provider: AuthorizedNetworking) -> Observable<[Card]> { - let endpoint = ArtsyAuthenticatedAPI.MyCreditCards + func creditCard(_ provider: AuthorizedNetworking) -> Observable<[Card]> { + let endpoint = ArtsyAuthenticatedAPI.myCreditCards return provider .request(endpoint) .filterSuccessfulStatusCodes() @@ -166,9 +166,9 @@ class ConfirmYourBidArtsyLoginViewController: UIViewController { .mapToObjectArray(Card.self) } - @IBAction func useBidderTapped(sender: AnyObject) { + @IBAction func useBidderTapped(_ sender: AnyObject) { for controller in navigationController!.viewControllers { - if controller.isKindOfClass(ConfirmYourBidViewController.self) { + if controller.isKind(of: ConfirmYourBidViewController.self) { navigationController!.popToViewController(controller, animated:true); break; } @@ -178,11 +178,11 @@ class ConfirmYourBidArtsyLoginViewController: UIViewController { private extension ConfirmYourBidArtsyLoginViewController { - @IBAction func dev_hasCardTapped(sender: AnyObject) { + @IBAction func dev_hasCardTapped(_ sender: AnyObject) { self.performSegue(.EmailLoginConfirmedHighestBidder) } - @IBAction func dev_noCardFoundTapped(sender: AnyObject) { + @IBAction func dev_noCardFoundTapped(_ sender: AnyObject) { self.performSegue(.ArtsyUserHasNotRegisteredCard) } diff --git a/Kiosk/Bid Fulfillment/ConfirmYourBidEnterYourEmailViewController.swift b/Kiosk/Bid Fulfillment/ConfirmYourBidEnterYourEmailViewController.swift index 97046d59..643489e6 100644 --- a/Kiosk/Bid Fulfillment/ConfirmYourBidEnterYourEmailViewController.swift +++ b/Kiosk/Bid Fulfillment/ConfirmYourBidEnterYourEmailViewController.swift @@ -9,7 +9,7 @@ class ConfirmYourBidEnterYourEmailViewController: UIViewController { @IBOutlet var confirmButton: UIButton! @IBOutlet var bidDetailsPreviewView: BidDetailsPreviewView! - class func instantiateFromStoryboard(storyboard: UIStoryboard) -> ConfirmYourBidEnterYourEmailViewController { + class func instantiateFromStoryboard(_ storyboard: UIStoryboard) -> ConfirmYourBidEnterYourEmailViewController { return storyboard.viewControllerWithID(.ConfirmYourBidEnterEmail) as! ConfirmYourBidEnterYourEmailViewController } @@ -58,20 +58,20 @@ class ConfirmYourBidEnterYourEmailViewController: UIViewController { }.addDisposableTo(rx_disposeBag) } - override func viewWillAppear(animated: Bool) { + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) self.emailTextField.becomeFirstResponder() } - override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { - super.prepareForSegue(segue, sender: sender) + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + super.prepare(for: segue, sender: sender) if segue == .EmailNotFoundonArtsy { - let viewController = segue.destinationViewController as! RegisterViewController + let viewController = segue.destination as! RegisterViewController viewController.provider = provider } else if segue == .ExistingArtsyUserFound { - let viewController = segue.destinationViewController as! ConfirmYourBidArtsyLoginViewController + let viewController = segue.destination as! ConfirmYourBidArtsyLoginViewController viewController.provider = provider } } @@ -79,12 +79,12 @@ class ConfirmYourBidEnterYourEmailViewController: UIViewController { private extension ConfirmYourBidEnterYourEmailViewController { - @IBAction func dev_emailFound(sender: AnyObject) { + @IBAction func dev_emailFound(_ sender: AnyObject) { performSegue(.ExistingArtsyUserFound) } - @IBAction func dev_emailNotFound(sender: AnyObject) { + @IBAction func dev_emailNotFound(_ sender: AnyObject) { performSegue(.EmailNotFoundonArtsy) } -} \ No newline at end of file +} diff --git a/Kiosk/Bid Fulfillment/ConfirmYourBidPINViewController.swift b/Kiosk/Bid Fulfillment/ConfirmYourBidPINViewController.swift index 00c301b3..8f87860b 100644 --- a/Kiosk/Bid Fulfillment/ConfirmYourBidPINViewController.swift +++ b/Kiosk/Bid Fulfillment/ConfirmYourBidPINViewController.swift @@ -5,7 +5,7 @@ import Action class ConfirmYourBidPINViewController: UIViewController { - private var _pin = Variable("") + fileprivate var _pin = Variable("") @IBOutlet var keypadContainer: KeypadContainerView! @IBOutlet var pinTextField: TextField! @@ -17,7 +17,8 @@ class ConfirmYourBidPINViewController: UIViewController { var provider: Networking! - class func instantiateFromStoryboard(storyboard: UIStoryboard) -> ConfirmYourBidPINViewController { + // TODO: These all need to be changed. + class func instantiateFromStoryboard(_ storyboard: UIStoryboard) -> ConfirmYourBidPINViewController { return storyboard.viewControllerWithID(.ConfirmYourBidPIN) as! ConfirmYourBidPINViewController } @@ -97,29 +98,29 @@ class ConfirmYourBidPINViewController: UIViewController { } } - override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { - super.prepareForSegue(segue, sender: sender) + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + super.prepare(for: segue, sender: sender) if segue == .ArtsyUserviaPINHasNotRegisteredCard { - let viewController = segue.destinationViewController as! RegisterViewController + let viewController = segue.destination as! RegisterViewController viewController.provider = provider } else if segue == .PINConfirmedhasCard { - let viewController = segue.destinationViewController as! LoadingViewController + let viewController = segue.destination as! LoadingViewController viewController.provider = provider } } - @IBAction func forgotPINTapped(sender: AnyObject) { + @IBAction func forgotPINTapped(_ sender: AnyObject) { let auctionID = fulfillmentNav().auctionID let number = fulfillmentNav().bidDetails.newUser.phoneNumber.value ?? "" let endpoint: ArtsyAPI = ArtsyAPI.BidderDetailsNotification(auctionID: auctionID, identifier: number) - let alertController = UIAlertController(title: "Forgot PIN", message: "We have sent your bidder details to your device.", preferredStyle: .Alert) + let alertController = UIAlertController(title: "Forgot PIN", message: "We have sent your bidder details to your device.", preferredStyle: .alert) - let cancelAction = UIAlertAction(title: "Back", style: .Cancel) { (_) in } + let cancelAction = UIAlertAction(title: "Back", style: .cancel) { (_) in } alertController.addAction(cancelAction) - self.presentViewController(alertController, animated: true) {} + self.present(alertController, animated: true) {} provider.request(endpoint) .filterSuccessfulStatusCodes() @@ -138,11 +139,11 @@ class ConfirmYourBidPINViewController: UIViewController { } func checkForCreditCard(loggedInProvider: AuthorizedNetworking) -> Observable<[Card]> { - let endpoint = ArtsyAuthenticatedAPI.MyCreditCards + let endpoint = ArtsyAuthenticatedAPI.myCreditCards return loggedInProvider.request(endpoint).filterSuccessfulStatusCodes().mapJSON().mapToObjectArray(Card.self) } - func gotCards(cards: [Card]) { + func got(cards: [Card]) { // If the cards list doesn't exist, or its .empty, then perform the segue to collect one. // Otherwise, proceed directly to the loading view controller to place the bid. if cards.isEmpty { @@ -154,7 +155,7 @@ class ConfirmYourBidPINViewController: UIViewController { } private extension ConfirmYourBidPINViewController { - @IBAction func dev_loggedInTapped(sender: AnyObject) { + @IBAction func dev_loggedInTapped(_ sender: AnyObject) { self.performSegue(.PINConfirmedhasCard) } -} \ No newline at end of file +} diff --git a/Kiosk/Bid Fulfillment/ConfirmYourBidPasswordViewController.swift b/Kiosk/Bid Fulfillment/ConfirmYourBidPasswordViewController.swift index c565110a..177b805e 100644 --- a/Kiosk/Bid Fulfillment/ConfirmYourBidPasswordViewController.swift +++ b/Kiosk/Bid Fulfillment/ConfirmYourBidPasswordViewController.swift @@ -6,7 +6,7 @@ class ConfirmYourBidPasswordViewController: UIViewController { @IBOutlet var bidDetailsPreviewView: BidDetailsPreviewView! - class func instantiateFromStoryboard(storyboard: UIStoryboard) -> ConfirmYourBidPasswordViewController { + class func instantiateFromStoryboard(_ storyboard: UIStoryboard) -> ConfirmYourBidPasswordViewController { return storyboard.viewControllerWithID(.ConfirmYourBid) as! ConfirmYourBidPasswordViewController } @@ -16,7 +16,7 @@ class ConfirmYourBidPasswordViewController: UIViewController { bidDetailsPreviewView.bidDetails = fulfillmentNav().bidDetails } - @IBAction func dev_noPhoneNumberFoundTapped(sender: AnyObject) { + @IBAction func dev_noPhoneNumberFoundTapped(_ sender: AnyObject) { } diff --git a/Kiosk/Bid Fulfillment/ConfirmYourBidViewController.swift b/Kiosk/Bid Fulfillment/ConfirmYourBidViewController.swift index cd572549..e48752d4 100644 --- a/Kiosk/Bid Fulfillment/ConfirmYourBidViewController.swift +++ b/Kiosk/Bid Fulfillment/ConfirmYourBidViewController.swift @@ -6,7 +6,7 @@ import Action class ConfirmYourBidViewController: UIViewController { - private var _number = Variable("") + fileprivate var _number = Variable("") let phoneNumberFormatter = ECPhoneNumberFormatter() @IBOutlet var bidDetailsPreviewView: BidDetailsPreviewView! @@ -16,7 +16,7 @@ class ConfirmYourBidViewController: UIViewController { @IBOutlet var enterButton: UIButton! @IBOutlet var useArtsyLoginButton: UIButton! - private let _viewWillDisappear = PublishSubject() + fileprivate let _viewWillDisappear = PublishSubject() var viewWillDisappear: Observable { return self._viewWillDisappear.asObserver() } @@ -26,18 +26,18 @@ class ConfirmYourBidViewController: UIViewController { var provider: Networking! - class func instantiateFromStoryboard(storyboard: UIStoryboard) -> ConfirmYourBidViewController { + class func instantiateFromStoryboard(_ storyboard: UIStoryboard) -> ConfirmYourBidViewController { return storyboard.viewControllerWithID(.ConfirmYourBid) as! ConfirmYourBidViewController } override func viewDidLoad() { super.viewDidLoad() - let titleString = useArtsyLoginButton.titleForState(useArtsyLoginButton.state)! ?? "" - let attributes = [NSUnderlineStyleAttributeName: NSUnderlineStyle.StyleSingle.rawValue, - NSFontAttributeName: useArtsyLoginButton.titleLabel!.font]; + let titleString = useArtsyLoginButton.title(for: useArtsyLoginButton.state)! ?? "" + let attributes = [NSUnderlineStyleAttributeName: NSUnderlineStyle.styleSingle.rawValue, + NSFontAttributeName: useArtsyLoginButton.titleLabel!.font] as [String : Any]; let attrTitle = NSAttributedString(string: titleString, attributes:attributes) - useArtsyLoginButton.setAttributedTitle(attrTitle, forState:useArtsyLoginButton.state) + useArtsyLoginButton.setAttributedTitle(attrTitle, for:useArtsyLoginButton.state) number .bindTo(_number) @@ -90,7 +90,7 @@ class ConfirmYourBidViewController: UIViewController { } if let responseURL = response?.response?.URL?.absoluteString - where responseURL.containsString("v1/bidder/") { + , responseURL.containsString("v1/bidder/") { me.performSegue(.ConfirmyourBidBidderFound) } else { @@ -101,38 +101,38 @@ class ConfirmYourBidViewController: UIViewController { }) } - override func viewWillDisappear(animated: Bool) { + override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) _viewWillDisappear.onNext() } - override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue == .ConfirmyourBidBidderFound { - let nextViewController = segue.destinationViewController as! ConfirmYourBidPINViewController + let nextViewController = segue.destination as! ConfirmYourBidPINViewController nextViewController.provider = provider } else if segue == .ConfirmyourBidBidderNotFound { - let viewController = segue.destinationViewController as! ConfirmYourBidEnterYourEmailViewController + let viewController = segue.destination as! ConfirmYourBidEnterYourEmailViewController viewController.provider = provider } else if segue == .ConfirmyourBidArtsyLogin { - let viewController = segue.destinationViewController as! ConfirmYourBidArtsyLoginViewController + let viewController = segue.destination as! ConfirmYourBidArtsyLoginViewController viewController.provider = provider } else if segue == .ConfirmyourBidBidderFound { - let viewController = segue.destinationViewController as! ConfirmYourBidPINViewController + let viewController = segue.destination as! ConfirmYourBidPINViewController viewController.provider = provider } } - func toOpeningBidString(cents:AnyObject!) -> AnyObject! { - if let dollars = NSNumberFormatter.currencyStringForCents(cents as? Int) { - return "Enter \(dollars) or more" + func toOpeningBidString(_ cents:AnyObject!) -> AnyObject! { + if let dollars = NumberFormatter.currencyString(forCents: cents as? Int as NSNumber!) { + return "Enter \(dollars) or more" as AnyObject! } - return "" + return "" as AnyObject! } - func toPhoneNumberString(number: String) -> String { + func toPhoneNumberString(_ number: String) -> String { if number.characters.count >= 7 { - return phoneNumberFormatter.stringForObjectValue(number) ?? number + return phoneNumberFormatter.string(for: number) ?? number } else { return number } @@ -142,11 +142,11 @@ class ConfirmYourBidViewController: UIViewController { private extension ConfirmYourBidViewController { - @IBAction func dev_noPhoneNumberFoundTapped(sender: AnyObject) { + @IBAction func dev_noPhoneNumberFoundTapped(_ sender: AnyObject) { self.performSegue(.ConfirmyourBidArtsyLogin ) } - @IBAction func dev_phoneNumberFoundTapped(sender: AnyObject) { + @IBAction func dev_phoneNumberFoundTapped(_ sender: AnyObject) { self.performSegue(.ConfirmyourBidBidderFound) } diff --git a/Kiosk/Bid Fulfillment/FulfillmentContainerViewController.swift b/Kiosk/Bid Fulfillment/FulfillmentContainerViewController.swift index a72537ea..2569c2a5 100644 --- a/Kiosk/Bid Fulfillment/FulfillmentContainerViewController.swift +++ b/Kiosk/Bid Fulfillment/FulfillmentContainerViewController.swift @@ -9,7 +9,7 @@ class FulfillmentContainerViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - modalPresentationStyle = UIModalPresentationStyle.OverCurrentContext + modalPresentationStyle = UIModalPresentationStyle.overCurrentContext contentView.alpha = 0 backgroundView.alpha = 0 @@ -21,21 +21,21 @@ class FulfillmentContainerViewController: UIViewController { // This is mostly a placeholder for a more complex animation in the future - func viewDidAppearAnimation(animated: Bool) { - self.contentView.frame = CGRectOffset(self.contentView.frame, 0, 100) + func viewDidAppearAnimation(_ animated: Bool) { + self.contentView.frame = self.contentView.frame.offsetBy(dx: 0, dy: 100) UIView.animateTwoStepIf(animated, duration: 0.3, { self.backgroundView.alpha = 1 }, midway: { self.contentView.alpha = 1 self.cancelButton.alpha = 1 - self.contentView.frame = CGRectOffset(self.contentView.frame, 0, -100) + self.contentView.frame = self.contentView.frame.offsetBy(dx: 0, dy: -100) }) { (complete) in } } - @IBAction func closeModalTapped(sender: AnyObject) { + @IBAction func closeModalTapped(_ sender: AnyObject) { closeFulfillmentModal() } @@ -47,7 +47,7 @@ class FulfillmentContainerViewController: UIViewController { }) { (completed:Bool) in let presentingVC = self.presentingViewController! - presentingVC.dismissViewControllerAnimated(false, completion: nil) + presentingVC.dismiss(animated: false, completion: nil) completion?() } } @@ -58,7 +58,7 @@ class FulfillmentContainerViewController: UIViewController { return self.childViewControllers.first as? FulfillmentNavigationController } - class func instantiateFromStoryboard(storyboard: UIStoryboard) -> FulfillmentContainerViewController { + class func instantiateFromStoryboard(_ storyboard: UIStoryboard) -> FulfillmentContainerViewController { return storyboard.viewControllerWithID(.FulfillmentContainer) as! FulfillmentContainerViewController } } diff --git a/Kiosk/Bid Fulfillment/FulfillmentNavigationController.swift b/Kiosk/Bid Fulfillment/FulfillmentNavigationController.swift index eb9ab212..e3ff00d9 100644 --- a/Kiosk/Bid Fulfillment/FulfillmentNavigationController.swift +++ b/Kiosk/Bid Fulfillment/FulfillmentNavigationController.swift @@ -30,13 +30,13 @@ class FulfillmentNavigationController: UINavigationController, FulfillmentContro } func reset() { - let storage = NSHTTPCookieStorage.sharedHTTPCookieStorage() + let storage = HTTPCookieStorage.shared let cookies = storage.cookies cookies?.forEach { storage.deleteCookie($0) } } func updateUserCredentials(loggedInProvider: AuthorizedNetworking) -> Observable { - let endpoint = ArtsyAuthenticatedAPI.Me + let endpoint = ArtsyAuthenticatedAPI.me let request = loggedInProvider.request(endpoint).filterSuccessfulStatusCodes().mapJSON().mapToObject(User) return request @@ -58,7 +58,7 @@ class FulfillmentNavigationController: UINavigationController, FulfillmentContro } extension FulfillmentNavigationController: UINavigationControllerDelegate { - func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) { + func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) { guard let viewController = viewController as? PlaceBidViewController else { return } viewController.provider = provider diff --git a/Kiosk/Bid Fulfillment/KeypadContainerView.swift b/Kiosk/Bid Fulfillment/KeypadContainerView.swift index 047c3b26..8d4bf50d 100644 --- a/Kiosk/Bid Fulfillment/KeypadContainerView.swift +++ b/Kiosk/Bid Fulfillment/KeypadContainerView.swift @@ -5,8 +5,8 @@ import Action //@IBDesignable class KeypadContainerView: UIView { - private var keypad: KeypadView! - private let viewModel = KeypadViewModel() + fileprivate var keypad: KeypadView! + fileprivate let viewModel = KeypadViewModel() var stringValue: Observable! var intValue: Observable! @@ -16,8 +16,8 @@ class KeypadContainerView: UIView { override func prepareForInterfaceBuilder() { for subview in subviews { subview.removeFromSuperview() } - let bundle = NSBundle(forClass: self.dynamicType) - let image = UIImage(named: "KeypadViewPreviewIB", inBundle: bundle, compatibleWithTraitCollection: self.traitCollection) + let bundle = Bundle(for: type(of: self)) + let image = UIImage(named: "KeypadViewPreviewIB", in: bundle, compatibleWith: self.traitCollection) let imageView = UIImageView(frame: self.bounds) imageView.image = image @@ -27,7 +27,7 @@ class KeypadContainerView: UIView { override func awakeFromNib() { super.awakeFromNib() - keypad = NSBundle(forClass: self.dynamicType).loadNibNamed("KeypadView", owner: self, options: nil).first as? KeypadView + keypad = Bundle(for: type(of: self)).loadNibNamed("KeypadView", owner: self, options: nil)?.first as? KeypadView keypad.leftAction = viewModel.deleteAction keypad.rightAction = viewModel.clearAction keypad.keyAction = viewModel.addDigitAction diff --git a/Kiosk/Bid Fulfillment/KeypadView.swift b/Kiosk/Bid Fulfillment/KeypadView.swift index 8cf2d320..fc422da5 100644 --- a/Kiosk/Bid Fulfillment/KeypadView.swift +++ b/Kiosk/Bid Fulfillment/KeypadView.swift @@ -16,11 +16,11 @@ class KeypadView: UIView { var keyAction: Action? - @IBOutlet private var keys: [Button]! - @IBOutlet private var leftButton: Button! - @IBOutlet private var rightButton: Button! + @IBOutlet fileprivate var keys: [Button]! + @IBOutlet fileprivate var leftButton: Button! + @IBOutlet fileprivate var rightButton: Button! - @IBAction func keypadButtonTapped(sender: UIButton) { + @IBAction func keypadButtonTapped(_ sender: UIButton) { keyAction?.execute(sender.tag) } } diff --git a/Kiosk/Bid Fulfillment/KeypadViewModel.swift b/Kiosk/Bid Fulfillment/KeypadViewModel.swift index 5792b6cf..b9e8fb21 100644 --- a/Kiosk/Bid Fulfillment/KeypadViewModel.swift +++ b/Kiosk/Bid Fulfillment/KeypadViewModel.swift @@ -59,7 +59,7 @@ private extension KeypadViewModel { } } - func addDigit(input: Int) -> Observable { + func addDigit(_ input: Int) -> Observable { return Observable.create { [weak self] observer in if let strongSelf = self { let newValue = (10 * strongSelf.intValue.value) + input diff --git a/Kiosk/Bid Fulfillment/LoadingViewController.swift b/Kiosk/Bid Fulfillment/LoadingViewController.swift index 7f5a75b6..593e44c0 100644 --- a/Kiosk/Bid Fulfillment/LoadingViewController.swift +++ b/Kiosk/Bid Fulfillment/LoadingViewController.swift @@ -21,7 +21,7 @@ class LoadingViewController: UIViewController { @IBOutlet weak var backToAuctionButton: SecondaryActionButton! @IBOutlet weak var placeHigherBidButton: ActionButton! - private let _viewWillDisappear = PublishSubject() + fileprivate let _viewWillDisappear = PublishSubject() var viewWillDisappear: Observable { return self._viewWillDisappear.asObserver() } @@ -47,19 +47,19 @@ class LoadingViewController: UIViewController { if placingBid { bidDetailsPreviewView.bidDetails = viewModel.bidDetails } else { - bidDetailsPreviewView.hidden = true + bidDetailsPreviewView.isHidden = true } - statusMessage.hidden = true - backToAuctionButton.hidden = true - placeHigherBidButton.hidden = true + statusMessage.isHidden = true + backToAuctionButton.isHidden = true + placeHigherBidButton.isHidden = true spinner.animate(animate) titleLabel.text = placingBid ? "Placing bid..." : "Registering..." // Either finishUp() or bidderError() are responsible for providing a way back to the auction. - fulfillmentContainer()?.cancelButton.hidden = true + fulfillmentContainer()?.cancelButton.isHidden = true // The view model will perform actions like registering a user if necessary, // placing a bid if requested, and polling for results. @@ -79,20 +79,20 @@ class LoadingViewController: UIViewController { .addDisposableTo(rx_disposeBag) } - override func viewWillDisappear(animated: Bool) { + override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) _viewWillDisappear.onNext() } - override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue == .PushtoRegisterConfirmed { - let detailsVC = segue.destinationViewController as! YourBiddingDetailsViewController + let detailsVC = segue.destination as! YourBiddingDetailsViewController detailsVC.confirmationImage = bidConfirmationImageView.image detailsVC.provider = provider } if segue == .PlaceaHigherBidAfterNotBeingHighestBidder { - let placeBidVC = segue.destinationViewController as! PlaceBidViewController + let placeBidVC = segue.destination as! PlaceBidViewController placeBidVC.hasAlreadyPlacedABid = true placeBidVC.provider = provider } @@ -142,13 +142,13 @@ extension LoadingViewController { let title = reserveNotMet ? "NO, THANKS" : (createdNewBidder ? "CONTINUE" : "BACK TO AUCTION") backToAuctionButton.setTitle(title, forState: .Normal) - fulfillmentContainer()?.cancelButton.hidden = false + fulfillmentContainer()?.cancelButton.isHidden = false } func handleRegistered() { titleLabel.text = "Registration Complete" bidConfirmationImageView.image = UIImage(named: "BidHighestBidder") - fulfillmentContainer()?.cancelButton.setTitle("DONE", forState: .Normal) + fulfillmentContainer()?.cancelButton.setTitle("DONE", for: UIControlState()) Observable.interval(1, scheduler: MainScheduler.instance) .take(1) .subscribeCompleted { [weak self] in @@ -160,7 +160,7 @@ extension LoadingViewController { func handleUpdate() { titleLabel.text = "Updated your Information" bidConfirmationImageView.image = UIImage(named: "BidHighestBidder") - fulfillmentContainer()?.cancelButton.setTitle("DONE", forState: .Normal) + fulfillmentContainer()?.cancelButton.setTitle("DONE", for: UIControlState()) } func handleUnknownBidder() { @@ -170,14 +170,14 @@ extension LoadingViewController { func handleReserveNotMet() { titleLabel.text = "Reserve Not Met" - statusMessage.hidden = false + statusMessage.isHidden = false statusMessage.text = "Your bid is still below this lot's reserve. Please place a higher bid." bidConfirmationImageView.image = UIImage(named: "BidNotHighestBidder") } func handleHighestBidder() { titleLabel.text = "High Bid!" - statusMessage.hidden = false + statusMessage.isHidden = false statusMessage.text = "You are the high bidder for this lot." bidConfirmationImageView.image = UIImage(named: "BidHighestBidder") @@ -185,24 +185,24 @@ extension LoadingViewController { self?.closeSelf() }.addDisposableTo(rx_disposeBag) - bidConfirmationImageView.userInteractionEnabled = true + bidConfirmationImageView.isUserInteractionEnabled = true bidConfirmationImageView.addGestureRecognizer(recognizer) - fulfillmentContainer()?.cancelButton.setTitle("DONE", forState: .Normal) + fulfillmentContainer()?.cancelButton.setTitle("DONE", for: UIControlState()) } func handleLowestBidder() { titleLabel.text = "Higher bid needed" titleLabel.textColor = .artsyRed() - statusMessage.hidden = false + statusMessage.isHidden = false statusMessage.text = "Another bidder has placed a higher maximum bid. Place a higher bid to secure the lot." bidConfirmationImageView.image = UIImage(named: "BidNotHighestBidder") - placeHigherBidButton.hidden = false + placeHigherBidButton.isHidden = false } // MARK: - Error Handling - func bidderError(error: NSError) { + func bidderError(_ error: NSError) { if placingBid { // If you are bidding, we show a bidding error regardless of whether or not you're also registering. if error.domain == OutbidDomain { @@ -228,24 +228,24 @@ extension LoadingViewController { error: error) } - func handleErrorWithTitle(title: String, message: String, error: NSError) { + func handleError(withTitle title: String, message: String, error: NSError) { titleLabel.textColor = .artsyRed() titleLabel.text = title statusMessage.text = message - statusMessage.hidden = false - backToAuctionButton.hidden = false + statusMessage.isHidden = false + backToAuctionButton.isHidden = false statusMessage.presentOnLongPress("Error: \(error.localizedDescription). \n \(error.artsyServerError())", title: title) { [weak self] alertController in - self?.presentViewController(alertController, animated: true, completion: nil) + self?.present(alertController, animated: true, completion: nil) } } - @IBAction func placeHigherBidTapped(sender: AnyObject) { + @IBAction func placeHigherBidTapped(_ sender: AnyObject) { self.fulfillmentNav().bidDetails.bidAmountCents.value = 0 self.performSegue(.PlaceaHigherBidAfterNotBeingHighestBidder) } - @IBAction func backToAuctionTapped(sender: AnyObject) { + @IBAction func backToAuctionTapped(_ sender: AnyObject) { if viewModel.createdNewBidder.value { self.performSegue(.PushtoRegisterConfirmed) } else { diff --git a/Kiosk/Bid Fulfillment/ManualCreditCardInputViewController.swift b/Kiosk/Bid Fulfillment/ManualCreditCardInputViewController.swift index 960fa95f..9ab2cfec 100644 --- a/Kiosk/Bid Fulfillment/ManualCreditCardInputViewController.swift +++ b/Kiosk/Bid Fulfillment/ManualCreditCardInputViewController.swift @@ -31,9 +31,9 @@ class ManualCreditCardInputViewController: UIViewController, RegistrationSubCont override func viewDidLoad() { super.viewDidLoad() - expirationDateWrapperView.hidden = true - securityCodeWrapperView.hidden = true - billingZipWrapperView.hidden = true + expirationDateWrapperView.isHidden = true + securityCodeWrapperView.isHidden = true + billingZipWrapperView.isHidden = true // We show the enter credit card number, then the date switching the views around viewModel @@ -89,101 +89,101 @@ class ManualCreditCardInputViewController: UIViewController, RegistrationSubCont cardNumberTextField.becomeFirstResponder() } - func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool { + func textField(_ textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool { return viewModel.isEntryValid(string) } - @IBAction func cardNumberconfirmTapped(sender: AnyObject) { - cardNumberWrapperView.hidden = true - expirationDateWrapperView.hidden = false - securityCodeWrapperView.hidden = true - billingZipWrapperView.hidden = true + @IBAction func cardNumberconfirmTapped(_ sender: AnyObject) { + cardNumberWrapperView.isHidden = true + expirationDateWrapperView.isHidden = false + securityCodeWrapperView.isHidden = true + billingZipWrapperView.isHidden = true - expirationDateWrapperView.frame = CGRectMake(0, 0, CGRectGetWidth(expirationDateWrapperView.frame), CGRectGetHeight(expirationDateWrapperView.frame)) + expirationDateWrapperView.frame = CGRect(x: 0, y: 0, width: expirationDateWrapperView.frame.width, height: expirationDateWrapperView.frame.height) expirationMonthTextField.becomeFirstResponder() } - @IBAction func expirationDateConfirmTapped(sender: AnyObject) { - cardNumberWrapperView.hidden = true - expirationDateWrapperView.hidden = true - securityCodeWrapperView.hidden = false - billingZipWrapperView.hidden = true + @IBAction func expirationDateConfirmTapped(_ sender: AnyObject) { + cardNumberWrapperView.isHidden = true + expirationDateWrapperView.isHidden = true + securityCodeWrapperView.isHidden = false + billingZipWrapperView.isHidden = true - securityCodeWrapperView.frame = CGRectMake(0, 0, CGRectGetWidth(securityCodeWrapperView.frame), CGRectGetHeight(securityCodeWrapperView.frame)) + securityCodeWrapperView.frame = CGRect(x: 0, y: 0, width: securityCodeWrapperView.frame.width, height: securityCodeWrapperView.frame.height) securitycodeTextField.becomeFirstResponder() } - @IBAction func securityCodeConfirmTapped(sender: AnyObject) { - cardNumberWrapperView.hidden = true - expirationDateWrapperView.hidden = true - securityCodeWrapperView.hidden = true - billingZipWrapperView.hidden = false + @IBAction func securityCodeConfirmTapped(_ sender: AnyObject) { + cardNumberWrapperView.isHidden = true + expirationDateWrapperView.isHidden = true + securityCodeWrapperView.isHidden = true + billingZipWrapperView.isHidden = false - billingZipWrapperView.frame = CGRectMake(0, 0, CGRectGetWidth(billingZipWrapperView.frame), CGRectGetHeight(billingZipWrapperView.frame)) + billingZipWrapperView.frame = CGRect(x: 0, y: 0, width: billingZipWrapperView.frame.width, height: billingZipWrapperView.frame.height) billingZipTextField.becomeFirstResponder() } - @IBAction func backToCardNumber(sender: AnyObject) { - cardNumberWrapperView.hidden = false - expirationDateWrapperView.hidden = true - securityCodeWrapperView.hidden = true - billingZipWrapperView.hidden = true + @IBAction func backToCardNumber(_ sender: AnyObject) { + cardNumberWrapperView.isHidden = false + expirationDateWrapperView.isHidden = true + securityCodeWrapperView.isHidden = true + billingZipWrapperView.isHidden = true cardNumberTextField.becomeFirstResponder() } - @IBAction func backToExpirationDate(sender: AnyObject) { - cardNumberWrapperView.hidden = true - expirationDateWrapperView.hidden = false - securityCodeWrapperView.hidden = true - billingZipWrapperView.hidden = true + @IBAction func backToExpirationDate(_ sender: AnyObject) { + cardNumberWrapperView.isHidden = true + expirationDateWrapperView.isHidden = false + securityCodeWrapperView.isHidden = true + billingZipWrapperView.isHidden = true expirationMonthTextField.becomeFirstResponder() } - @IBAction func backToSecurityCode(sender: AnyObject) { - cardNumberWrapperView.hidden = true - expirationDateWrapperView.hidden = true - securityCodeWrapperView.hidden = false - billingZipWrapperView.hidden = true + @IBAction func backToSecurityCode(_ sender: AnyObject) { + cardNumberWrapperView.isHidden = true + expirationDateWrapperView.isHidden = true + securityCodeWrapperView.isHidden = false + billingZipWrapperView.isHidden = true securitycodeTextField.becomeFirstResponder() } - class func instantiateFromStoryboard(storyboard: UIStoryboard) -> ManualCreditCardInputViewController { + class func instantiateFromStoryboard(_ storyboard: UIStoryboard) -> ManualCreditCardInputViewController { return storyboard.viewControllerWithID(.ManualCardDetailsInput) as! ManualCreditCardInputViewController } } private extension ManualCreditCardInputViewController { - func applyCardWithSuccess(success: Bool) { + func applyCardWithSuccess(_ success: Bool) { cardNumberTextField.text = success ? "4242424242424242" : "4000000000000002" - cardNumberTextField.sendActionsForControlEvents(.AllEditingEvents) - cardConfirmButton.sendActionsForControlEvents(.TouchUpInside) + cardNumberTextField.sendActions(for: .allEditingEvents) + cardConfirmButton.sendActions(for: .touchUpInside) expirationMonthTextField.text = "04" - expirationMonthTextField.sendActionsForControlEvents(.AllEditingEvents) + expirationMonthTextField.sendActions(for: .allEditingEvents) expirationYearTextField.text = "2018" - expirationYearTextField.sendActionsForControlEvents(.AllEditingEvents) - dateConfirmButton.sendActionsForControlEvents(.TouchUpInside) + expirationYearTextField.sendActions(for: .allEditingEvents) + dateConfirmButton.sendActions(for: .touchUpInside) securitycodeTextField.text = "123" - securitycodeTextField.sendActionsForControlEvents(.AllEditingEvents) - securityCodeConfirmButton.sendActionsForControlEvents(.TouchUpInside) + securitycodeTextField.sendActions(for: .allEditingEvents) + securityCodeConfirmButton.sendActions(for: .touchUpInside) billingZipTextField.text = "10001" - billingZipTextField.sendActionsForControlEvents(.AllEditingEvents) - billingZipTextField.sendActionsForControlEvents(.TouchUpInside) + billingZipTextField.sendActions(for: .allEditingEvents) + billingZipTextField.sendActions(for: .touchUpInside) } - @IBAction func dev_creditCardOKTapped(sender: AnyObject) { + @IBAction func dev_creditCardOKTapped(_ sender: AnyObject) { applyCardWithSuccess(true) } - @IBAction func dev_creditCardFailTapped(sender: AnyObject) { + @IBAction func dev_creditCardFailTapped(_ sender: AnyObject) { applyCardWithSuccess(false) } -} \ No newline at end of file +} diff --git a/Kiosk/Bid Fulfillment/ManualCreditCardInputViewModel.swift b/Kiosk/Bid Fulfillment/ManualCreditCardInputViewModel.swift index 55dccd4a..bfcbe2c0 100644 --- a/Kiosk/Bid Fulfillment/ManualCreditCardInputViewModel.swift +++ b/Kiosk/Bid Fulfillment/ManualCreditCardInputViewModel.swift @@ -13,8 +13,8 @@ class ManualCreditCardInputViewModel: NSObject { var securityCode = Variable("") var billingZip = Variable("") - private(set) var bidDetails: BidDetails! - private(set) var finishedSubject: PublishSubject? + fileprivate(set) var bidDetails: BidDetails! + fileprivate(set) var finishedSubject: PublishSubject? /// Mark: - Public members @@ -66,18 +66,18 @@ class ManualCreditCardInputViewModel: NSObject { } - func isEntryValid(entry: String) -> Bool { + func isEntryValid(_ entry: String) -> Bool { // Allow delete if (entry.isEmpty) { return true } // the API doesn't accept chars - let notNumberChars = NSCharacterSet.decimalDigitCharacterSet().invertedSet; - return entry.stringByTrimmingCharactersInSet(notNumberChars).isNotEmpty + let notNumberChars = CharacterSet.decimalDigits.inverted; + return entry.trimmingCharacters(in: notNumberChars).isNotEmpty } /// MARK: - Private Methods - private func registerCard(newUser: NewUser) -> Observable { + fileprivate func registerCard(newUser: NewUser) -> Observable { let month = expirationMonth.value.toUIntWithDefault(0) let year = expirationYear.value.toUIntWithDefault(0) diff --git a/Kiosk/Bid Fulfillment/Models/RegistrationCoordinator.swift b/Kiosk/Bid Fulfillment/Models/RegistrationCoordinator.swift index 81e06036..b1557161 100644 --- a/Kiosk/Bid Fulfillment/Models/RegistrationCoordinator.swift +++ b/Kiosk/Bid Fulfillment/Models/RegistrationCoordinator.swift @@ -2,32 +2,32 @@ import UIKit import RxSwift enum RegistrationIndex { - case MobileVC - case EmailVC - case PasswordVC - case CreditCardVC - case ZipCodeVC - case ConfirmVC + case mobileVC + case emailVC + case passwordVC + case creditCardVC + case zipCodeVC + case confirmVC func toInt() -> Int { switch (self) { - case MobileVC: return 0 - case EmailVC: return 1 - case PasswordVC: return 1 - case ZipCodeVC: return 2 - case CreditCardVC: return 3 - case ConfirmVC: return 4 + case .mobileVC: return 0 + case .emailVC: return 1 + case .passwordVC: return 1 + case .zipCodeVC: return 2 + case .creditCardVC: return 3 + case .confirmVC: return 4 } } - static func fromInt(index:Int) -> RegistrationIndex { + static func fromInt(_ index:Int) -> RegistrationIndex { switch (index) { - case 0: return .MobileVC - case 1: return .EmailVC - case 1: return .PasswordVC - case 2: return .ZipCodeVC - case 3: return .CreditCardVC - default : return .ConfirmVC + case 0: return .mobileVC + case 1: return .emailVC + case 1: return .passwordVC + case 2: return .zipCodeVC + case 3: return .creditCardVC + default : return .confirmVC } } } @@ -37,60 +37,60 @@ class RegistrationCoordinator: NSObject { let currentIndex = Variable(0) var storyboard: UIStoryboard! - func viewControllerForIndex(index: RegistrationIndex) -> UIViewController { + func viewControllerForIndex(_ index: RegistrationIndex) -> UIViewController { currentIndex.value = index.toInt() switch index { - case .MobileVC: + case .mobileVC: return storyboard.viewControllerWithID(.RegisterMobile) - case .EmailVC: + case .emailVC: return storyboard.viewControllerWithID(.RegisterEmail) - case .PasswordVC: + case .passwordVC: return storyboard.viewControllerWithID(.RegisterPassword) - case .ZipCodeVC: + case .zipCodeVC: return storyboard.viewControllerWithID(.RegisterPostalorZip) - case .CreditCardVC: + case .creditCardVC: if AppSetup.sharedState.disableCardReader { return storyboard.viewControllerWithID(.ManualCardDetailsInput) } else { return storyboard.viewControllerWithID(.RegisterCreditCard) } - case .ConfirmVC: + case .confirmVC: return storyboard.viewControllerWithID(.RegisterConfirm) } } - func nextViewControllerForBidDetails(details: BidDetails) -> UIViewController { + func nextViewControllerForBidDetails(_ details: BidDetails) -> UIViewController { if notSet(details.newUser.phoneNumber.value) { - return viewControllerForIndex(.MobileVC) + return viewControllerForIndex(.mobileVC) } if notSet(details.newUser.email.value) { - return viewControllerForIndex(.EmailVC) + return viewControllerForIndex(.emailVC) } if notSet(details.newUser.password.value) { - return viewControllerForIndex(.PasswordVC) + return viewControllerForIndex(.passwordVC) } if notSet(details.newUser.zipCode.value) && AppSetup.sharedState.needsZipCode { - return viewControllerForIndex(.ZipCodeVC) + return viewControllerForIndex(.zipCodeVC) } if notSet(details.newUser.creditCardToken.value) { - return viewControllerForIndex(.CreditCardVC) + return viewControllerForIndex(.creditCardVC) } - return viewControllerForIndex(.ConfirmVC) + return viewControllerForIndex(.confirmVC) } } -private func notSet(string: String?) -> Bool { +private func notSet(_ string: String?) -> Bool { return string?.isEmpty ?? true } diff --git a/Kiosk/Bid Fulfillment/PlaceBidNetworkModel.swift b/Kiosk/Bid Fulfillment/PlaceBidNetworkModel.swift index dffad31e..ad002964 100644 --- a/Kiosk/Bid Fulfillment/PlaceBidNetworkModel.swift +++ b/Kiosk/Bid Fulfillment/PlaceBidNetworkModel.swift @@ -8,7 +8,7 @@ let OutbidDomain = "Outbid" protocol PlaceBidNetworkModelType { var bidDetails: BidDetails { get } - func bid(provider: AuthorizedNetworking) -> Observable + func bid(_ provider: AuthorizedNetworking) -> Observable } class PlaceBidNetworkModel: NSObject, PlaceBidNetworkModelType { @@ -21,7 +21,7 @@ class PlaceBidNetworkModel: NSObject, PlaceBidNetworkModelType { super.init() } - func bid(provider: AuthorizedNetworking) -> Observable { + func bid(_ provider: AuthorizedNetworking) -> Observable { let saleArtwork = bidDetails.saleArtwork.value assert(saleArtwork.hasValue, "Sale artwork cannot nil at bidding stage.") @@ -30,7 +30,7 @@ class PlaceBidNetworkModel: NSObject, PlaceBidNetworkModelType { return bidOnSaleArtwork(saleArtwork!, bidAmountCents: String(cents), provider: provider) } - private func bidOnSaleArtwork(saleArtwork: SaleArtwork, bidAmountCents: String, provider: AuthorizedNetworking) -> Observable { + fileprivate func bidOnSaleArtwork(_ saleArtwork: SaleArtwork, bidAmountCents: String, provider: AuthorizedNetworking) -> Observable { let bidEndpoint = ArtsyAuthenticatedAPI.PlaceABid(auctionID: saleArtwork.auctionID!, artworkID: saleArtwork.artwork.id, maxBidCents: bidAmountCents) let request = provider @@ -51,7 +51,7 @@ class PlaceBidNetworkModel: NSObject, PlaceBidNetworkModelType { let json = JSON(data: response.data) - if let type = json["type"].string where type == "param_error" { + if let type = json["type"].string , type == "param_error" { throw NSError(domain: OutbidDomain, code: 0, userInfo: [NSUnderlyingErrorKey: error as NSError]) } else { throw error diff --git a/Kiosk/Bid Fulfillment/PlaceBidViewController.swift b/Kiosk/Bid Fulfillment/PlaceBidViewController.swift index 93d22673..13faad30 100644 --- a/Kiosk/Bid Fulfillment/PlaceBidViewController.swift +++ b/Kiosk/Bid Fulfillment/PlaceBidViewController.swift @@ -10,7 +10,7 @@ class PlaceBidViewController: UIViewController { var provider: Networking! - private var _bidDollars = Variable(0) + fileprivate var _bidDollars = Variable(0) var hasAlreadyPlacedABid: Bool = false @IBOutlet var bidAmountTextField: TextField! @@ -44,11 +44,11 @@ class PlaceBidViewController: UIViewController { lazy var bidDollars: Observable = { self.keypadContainer.intValue }() var buyersPremium: () -> (BuyersPremium?) = { appDelegate().sale.buyersPremium } - class func instantiateFromStoryboard(storyboard: UIStoryboard) -> PlaceBidViewController { + class func instantiateFromStoryboard(_ storyboard: UIStoryboard) -> PlaceBidViewController { return storyboard.viewControllerWithID(.PlaceYourBid) as! PlaceBidViewController } - private let _viewWillDisappear = PublishSubject() + fileprivate let _viewWillDisappear = PublishSubject() var viewWillDisappear: Observable { return self._viewWillDisappear.asObserver() } @@ -60,8 +60,8 @@ class PlaceBidViewController: UIViewController { self.fulfillmentNav().reset() } - currentBidTitleLabel.font = UIFont.serifSemiBoldFontWithSize(17) - yourBidTitleLabel.font = UIFont.serifSemiBoldFontWithSize(17) + currentBidTitleLabel.font = UIFont.serifSemiBoldFont(withSize: 17) + yourBidTitleLabel.font = UIFont.serifSemiBoldFont(withSize: 17) conditionsOfSaleButton.rx_action = showConditionsOfSaleCommand() privacyPolictyButton.rx_action = showPrivacyPolicyCommand() @@ -117,19 +117,19 @@ class PlaceBidViewController: UIViewController { enum LabelTags: Int { - case LotNumber = 1 - case ArtistName - case ArtworkTitle - case ArtworkPrice - case BuyersPremium - case Gobbler + case lotNumber = 1 + case artistName + case artworkTitle + case artworkPrice + case buyersPremium + case gobbler } let lotNumber = nav.bidDetails.saleArtwork?.lotNumber if let _ = lotNumber { let lotNumberLabel = smallSansSerifLabel() - lotNumberLabel.tag = LabelTags.LotNumber.rawValue + lotNumberLabel.tag = LabelTags.lotNumber.rawValue detailsStackView.addSubview(lotNumberLabel, withTopMargin: "10", sideMargin: "0") saleArtwork.viewModel .lotNumber() @@ -141,44 +141,44 @@ class PlaceBidViewController: UIViewController { } let artistNameLabel = sansSerifLabel() - artistNameLabel.tag = LabelTags.ArtistName.rawValue + artistNameLabel.tag = LabelTags.artistName.rawValue detailsStackView.addSubview(artistNameLabel, withTopMargin: "15", sideMargin: "0") let artworkTitleLabel = serifLabel() - artworkTitleLabel.tag = LabelTags.ArtworkTitle.rawValue + artworkTitleLabel.tag = LabelTags.artworkTitle.rawValue detailsStackView.addSubview(artworkTitleLabel, withTopMargin: "15", sideMargin: "0") let artworkPriceLabel = serifLabel() - artworkPriceLabel.tag = LabelTags.ArtworkPrice.rawValue + artworkPriceLabel.tag = LabelTags.artworkPrice.rawValue detailsStackView.addSubview(artworkPriceLabel, withTopMargin: "15", sideMargin: "0") if let _ = buyersPremium() { let buyersPremiumView = UIView() - buyersPremiumView.tag = LabelTags.BuyersPremium.rawValue + buyersPremiumView.tag = LabelTags.buyersPremium.rawValue let buyersPremiumLabel = ARSerifLabel() - buyersPremiumLabel.font = buyersPremiumLabel.font.fontWithSize(16) + buyersPremiumLabel.font = buyersPremiumLabel.font.withSize(16) buyersPremiumLabel.text = "This work has a " buyersPremiumLabel.textColor = .artsyHeavyGrey() let buyersPremiumButton = ARUnderlineButton() buyersPremiumButton.titleLabel?.font = buyersPremiumLabel.font - buyersPremiumButton.setTitle("buyers premium", forState: .Normal) - buyersPremiumButton.setTitleColor(.artsyHeavyGrey(), forState: .Normal) + buyersPremiumButton.setTitle("buyers premium", for: UIControlState()) + buyersPremiumButton.setTitleColor(.artsyHeavyGrey(), for: UIControlState()) buyersPremiumButton.rx_action = showBuyersPremiumCommand() buyersPremiumView.addSubview(buyersPremiumLabel) buyersPremiumView.addSubview(buyersPremiumButton) - buyersPremiumLabel.alignTop("0", leading: "0", bottom: "0", trailing: nil, toView: buyersPremiumView) - buyersPremiumLabel.alignBaselineWithView(buyersPremiumButton, predicate: nil) - buyersPremiumButton.alignAttribute(.Left, toAttribute: .Right, ofView: buyersPremiumLabel, predicate: "0") + buyersPremiumLabel.alignTop("0", leading: "0", bottom: "0", trailing: nil, to: buyersPremiumView) + buyersPremiumLabel.alignBaseline(with: buyersPremiumButton, predicate: nil) + buyersPremiumButton.alignAttribute(.left, to: .right, of: buyersPremiumLabel, predicate: "0") detailsStackView.addSubview(buyersPremiumView, withTopMargin: "15", sideMargin: "0") } let gobbler = WhitespaceGobbler() - gobbler.tag = LabelTags.Gobbler.rawValue + gobbler.tag = LabelTags.gobbler.rawValue detailsStackView.addSubview(gobbler, withTopMargin: "0") if let artist = saleArtwork.artwork.artists?.first { @@ -205,7 +205,7 @@ class PlaceBidViewController: UIViewController { .addDisposableTo(rx_disposeBag) if let url = saleArtwork.artwork.defaultImage?.thumbnailURL() { - self.artworkImageView.sd_setImageWithURL(url) + self.artworkImageView.sd_setImage(with: url as URL!) } else { self.artworkImageView.image = nil } @@ -213,25 +213,25 @@ class PlaceBidViewController: UIViewController { } } - override func viewWillDisappear(animated: Bool) { + override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) _viewWillDisappear.onNext() } - @IBAction func bidButtonTapped(sender: AnyObject) { + @IBAction func bidButtonTapped(_ sender: AnyObject) { let identifier = hasAlreadyPlacedABid ? SegueIdentifier.PlaceAnotherBid : SegueIdentifier.ConfirmBid performSegue(identifier) } - override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue == .PlaceAnotherBid { - let nextViewController = segue.destinationViewController as! LoadingViewController + let nextViewController = segue.destination as! LoadingViewController nextViewController.provider = provider nextViewController.placingBid = true } else if segue == .ConfirmBid { - let viewController = segue.destinationViewController as! ConfirmYourBidViewController + let viewController = segue.destination as! ConfirmYourBidViewController viewController.provider = provider } } @@ -240,7 +240,7 @@ class PlaceBidViewController: UIViewController { private extension PlaceBidViewController { func smallSansSerifLabel() -> UILabel { let label = sansSerifLabel() - label.font = label.font.fontWithSize(12) + label.font = label.font.withSize(12) return label } @@ -253,7 +253,7 @@ private extension PlaceBidViewController { func serifLabel() -> UILabel { let label = ARSerifLabel() label.numberOfLines = 1 - label.font = label.font.fontWithSize(16) + label.font = label.font.withSize(16) return label } } @@ -261,19 +261,19 @@ private extension PlaceBidViewController { /// These are for RAC only -func dollarsToCurrencyString(dollars: Int) -> String { +func dollarsToCurrencyString(_ dollars: Int) -> String { if dollars == 0 { return "" } - let formatter = NSNumberFormatter() - formatter.locale = NSLocale(localeIdentifier: "en_US") - formatter.numberStyle = .DecimalStyle - return formatter.stringFromNumber(dollars) ?? "" + let formatter = NumberFormatter() + formatter.locale = Locale(identifier: "en_US") + formatter.numberStyle = .decimal + return formatter.string(from: NSNumber(dollars)) ?? "" } -func toNextBidString(cents: Int) -> String { - guard let dollars = NSNumberFormatter.currencyStringForCents(cents) else { +func toNextBidString(_ cents: Int) -> String { + guard let dollars = NumberFormatter.currencyString(forCents: cents as NSNumber!) else { return "" } return "Enter \(dollars) or more" @@ -281,7 +281,7 @@ func toNextBidString(cents: Int) -> String { typealias DeveloperOnly = PlaceBidViewController extension DeveloperOnly { - @IBAction func dev_nextIncrementPressed(sender: AnyObject) { + @IBAction func dev_nextIncrementPressed(_ sender: AnyObject) { let bidDetails = (self.navigationController as? FulfillmentNavigationController)?.bidDetails bidDetails?.bidAmountCents.value = bidDetails?.saleArtwork?.minimumNextBidCents performSegue(SegueIdentifier.ConfirmBid) diff --git a/Kiosk/Bid Fulfillment/RegisterViewController.swift b/Kiosk/Bid Fulfillment/RegisterViewController.swift index ff56eada..a04f6231 100644 --- a/Kiosk/Bid Fulfillment/RegisterViewController.swift +++ b/Kiosk/Bid Fulfillment/RegisterViewController.swift @@ -20,7 +20,7 @@ class RegisterViewController: UIViewController { dynamic var placingBid = true - private let _viewWillDisappear = PublishSubject() + fileprivate let _viewWillDisappear = PublishSubject() var viewWillDisappear: Observable { return self._viewWillDisappear.asObserver() } @@ -66,7 +66,7 @@ class RegisterViewController: UIViewController { goToNextVC() } - override func viewWillDisappear(animated: Bool) { + override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) _viewWillDisappear.onNext() } @@ -76,7 +76,7 @@ class RegisterViewController: UIViewController { goToViewController(nextVC) } - func goToViewController(controller: UIViewController) { + func goToViewController(_ controller: UIViewController) { self.internalNavController()!.viewControllers = [controller] if let subscribableVC = controller as? RegistrationSubController { @@ -94,10 +94,10 @@ class RegisterViewController: UIViewController { } } - override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue == .ShowLoadingView { - let nextViewController = segue.destinationViewController as! LoadingViewController + let nextViewController = segue.destination as! LoadingViewController nextViewController.placingBid = placingBid nextViewController.provider = provider } diff --git a/Kiosk/Bid Fulfillment/RegistrationEmailViewController.swift b/Kiosk/Bid Fulfillment/RegistrationEmailViewController.swift index c5397014..2fb20c9a 100644 --- a/Kiosk/Bid Fulfillment/RegistrationEmailViewController.swift +++ b/Kiosk/Bid Fulfillment/RegistrationEmailViewController.swift @@ -13,7 +13,7 @@ class RegistrationEmailViewController: UIViewController, RegistrationSubControll }() - private let _viewWillDisappear = PublishSubject() + fileprivate let _viewWillDisappear = PublishSubject() var viewWillDisappear: Observable { return self._viewWillDisappear.asObserver() } @@ -36,13 +36,13 @@ class RegistrationEmailViewController: UIViewController, RegistrationSubControll emailTextField.becomeFirstResponder() } - override func viewWillDisappear(animated: Bool) { + override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) _viewWillDisappear.onNext() } - func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool { + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { // Allow delete if (string.isEmpty) { return true } @@ -51,7 +51,7 @@ class RegistrationEmailViewController: UIViewController, RegistrationSubControll return string != " " } - class func instantiateFromStoryboard(storyboard: UIStoryboard) -> RegistrationEmailViewController { + class func instantiateFromStoryboard(_ storyboard: UIStoryboard) -> RegistrationEmailViewController { return storyboard.viewControllerWithID(.RegisterEmail) as! RegistrationEmailViewController } } diff --git a/Kiosk/Bid Fulfillment/RegistrationMobileViewController.swift b/Kiosk/Bid Fulfillment/RegistrationMobileViewController.swift index 0c6d883b..ae170d03 100644 --- a/Kiosk/Bid Fulfillment/RegistrationMobileViewController.swift +++ b/Kiosk/Bid Fulfillment/RegistrationMobileViewController.swift @@ -12,7 +12,7 @@ class RegistrationMobileViewController: UIViewController, RegistrationSubControl return GenericFormValidationViewModel(isValid: numberIsValid, manualInvocation: self.numberTextField.rx_returnKey, finishedSubject: self.finished) }() - private let _viewWillDisappear = PublishSubject() + fileprivate let _viewWillDisappear = PublishSubject() var viewWillDisappear: Observable { return self._viewWillDisappear.asObserver() } @@ -36,23 +36,23 @@ class RegistrationMobileViewController: UIViewController, RegistrationSubControl numberTextField.becomeFirstResponder() } - override func viewWillDisappear(animated: Bool) { + override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) _viewWillDisappear.onNext() } - func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool { + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { // Allow delete if string.isEmpty { return true } // the API doesn't accept chars - let notNumberChars = NSCharacterSet.decimalDigitCharacterSet().invertedSet; - return string.stringByTrimmingCharactersInSet(notNumberChars).isNotEmpty + let notNumberChars = CharacterSet.decimalDigits.inverted; + return string.trimmingCharacters(in: notNumberChars).isNotEmpty } - class func instantiateFromStoryboard(storyboard: UIStoryboard) -> RegistrationMobileViewController { + class func instantiateFromStoryboard(_ storyboard: UIStoryboard) -> RegistrationMobileViewController { return storyboard.viewControllerWithID(.RegisterMobile) as! RegistrationMobileViewController } } diff --git a/Kiosk/Bid Fulfillment/RegistrationPasswordViewController.swift b/Kiosk/Bid Fulfillment/RegistrationPasswordViewController.swift index 842002d2..275b8352 100644 --- a/Kiosk/Bid Fulfillment/RegistrationPasswordViewController.swift +++ b/Kiosk/Bid Fulfillment/RegistrationPasswordViewController.swift @@ -14,7 +14,7 @@ class RegistrationPasswordViewController: UIViewController, RegistrationSubContr var provider: Networking! - private let _viewWillDisappear = PublishSubject() + fileprivate let _viewWillDisappear = PublishSubject() var viewWillDisappear: Observable { return self._viewWillDisappear.asObserver() } @@ -35,7 +35,7 @@ class RegistrationPasswordViewController: UIViewController, RegistrationSubContr override func viewDidLoad() { super.viewDidLoad() - forgotPasswordButton.hidden = false + forgotPasswordButton.isHidden = false let passwordText = passwordTextField.rx_text passwordText @@ -89,7 +89,7 @@ class RegistrationPasswordViewController: UIViewController, RegistrationSubContr passwordTextField.becomeFirstResponder() } - override func viewWillDisappear(animated: Bool) { + override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) _viewWillDisappear.onNext() } @@ -119,7 +119,7 @@ class RegistrationPasswordViewController: UIViewController, RegistrationSubContr passwordTextField.text = "" } - class func instantiateFromStoryboard(storyboard: UIStoryboard) -> RegistrationPasswordViewController { + class func instantiateFromStoryboard(_ storyboard: UIStoryboard) -> RegistrationPasswordViewController { return storyboard.viewControllerWithID(.RegisterPassword) as! RegistrationPasswordViewController } } diff --git a/Kiosk/Bid Fulfillment/RegistrationPasswordViewModel.swift b/Kiosk/Bid Fulfillment/RegistrationPasswordViewModel.swift index 6b9de774..10d12d9c 100644 --- a/Kiosk/Bid Fulfillment/RegistrationPasswordViewModel.swift +++ b/Kiosk/Bid Fulfillment/RegistrationPasswordViewModel.swift @@ -12,7 +12,7 @@ protocol RegistrationPasswordViewModelType { class RegistrationPasswordViewModel: RegistrationPasswordViewModelType { - private let password = Variable("") + fileprivate let password = Variable("") var action: CocoaAction! let provider: Networking @@ -70,7 +70,7 @@ class RegistrationPasswordViewModel: RegistrationPasswordViewModelType { } func userForgotPassword() -> Observable { - let endpoint = ArtsyAPI.LostPasswordNotification(email: email) + let endpoint = ArtsyAPI.lostPasswordNotification(email: email) return provider.request(endpoint) .filterSuccessfulStatusCodes() .map(void) diff --git a/Kiosk/Bid Fulfillment/RegistrationPostalZipViewController.swift b/Kiosk/Bid Fulfillment/RegistrationPostalZipViewController.swift index 76b954a5..2931de98 100644 --- a/Kiosk/Bid Fulfillment/RegistrationPostalZipViewController.swift +++ b/Kiosk/Bid Fulfillment/RegistrationPostalZipViewController.swift @@ -10,7 +10,7 @@ class RegistrationPostalZipViewController: UIViewController, RegistrationSubCont return GenericFormValidationViewModel(isValid: zipCodeIsValid, manualInvocation: self.zipCodeTextField.rx_returnKey, finishedSubject: self.finished) }() - private let _viewWillDisappear = PublishSubject() + fileprivate let _viewWillDisappear = PublishSubject() var viewWillDisappear: Observable { return self._viewWillDisappear.asObserver() } @@ -35,13 +35,13 @@ class RegistrationPostalZipViewController: UIViewController, RegistrationSubCont zipCodeTextField.becomeFirstResponder() } - override func viewWillDisappear(animated: Bool) { + override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) _viewWillDisappear.onNext() } - class func instantiateFromStoryboard(storyboard: UIStoryboard) -> RegistrationPostalZipViewController { + class func instantiateFromStoryboard(_ storyboard: UIStoryboard) -> RegistrationPostalZipViewController { return storyboard.viewControllerWithID(.RegisterPostalorZip) as! RegistrationPostalZipViewController } } diff --git a/Kiosk/Bid Fulfillment/StripeManager.swift b/Kiosk/Bid Fulfillment/StripeManager.swift index 9431f0c3..5b6e362c 100644 --- a/Kiosk/Bid Fulfillment/StripeManager.swift +++ b/Kiosk/Bid Fulfillment/StripeManager.swift @@ -3,7 +3,7 @@ import RxSwift import Stripe class StripeManager: NSObject { - var stripeClient = STPAPIClient.sharedClient() + var stripeClient = STPAPIClient.shared() func registerCard(digits: String, month: UInt, year: UInt, securityCode: String, postalCode: String) -> Observable { let card = STPCard() @@ -32,25 +32,25 @@ class StripeManager: NSObject { } } - func stringIsCreditCard(cardNumber: String) -> Bool { - return STPCard.validateCardNumber(cardNumber) + func stringIsCreditCard(_ cardNumber: String) -> Bool { + return STPCard.validateNumber(cardNumber) } } extension STPCardBrand { var name: String? { switch self { - case .Visa: + case .visa: return "Visa" - case .Amex: + case .amex: return "American Express" - case .MasterCard: + case .masterCard: return "MasterCard" - case .Discover: + case .discover: return "Discover" case .JCB: return "JCB" - case .DinersClub: + case .dinersClub: return "Diners Club" default: return nil diff --git a/Kiosk/Bid Fulfillment/SwipeCreditCardViewController.swift b/Kiosk/Bid Fulfillment/SwipeCreditCardViewController.swift index eb56f93c..5d8dad70 100644 --- a/Kiosk/Bid Fulfillment/SwipeCreditCardViewController.swift +++ b/Kiosk/Bid Fulfillment/SwipeCreditCardViewController.swift @@ -15,7 +15,7 @@ class SwipeCreditCardViewController: UIViewController, RegistrationSubController @IBOutlet weak var titleLabel: ARSerifLabel! - class func instantiateFromStoryboard(storyboard: UIStoryboard) -> SwipeCreditCardViewController { + class func instantiateFromStoryboard(_ storyboard: UIStoryboard) -> SwipeCreditCardViewController { return storyboard.viewControllerWithID(.RegisterCreditCard) as! SwipeCreditCardViewController } @@ -35,7 +35,7 @@ class SwipeCreditCardViewController: UIViewController, RegistrationSubController } }() - private let _viewWillDisappear = PublishSubject() + fileprivate let _viewWillDisappear = PublishSubject() var viewWillDisappear: Observable { return self._viewWillDisappear.asObserver() } @@ -108,24 +108,24 @@ class SwipeCreditCardViewController: UIViewController, RegistrationSubController bidDetails.newUser.swipedCreditCard = true } - override func viewWillDisappear(animated: Bool) { + override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) _viewWillDisappear.onNext() } - func setInProgress(show: Bool) { + func setInProgress(_ show: Bool) { illustrationImageView.alpha = show ? 0.1 : 1 - processingLabel.hidden = !show - spinner.hidden = !show + processingLabel.isHidden = !show + spinner.isHidden = !show } // Used only for development, in private extension for testing. - private lazy var stripeManager = StripeManager() + fileprivate lazy var stripeManager = StripeManager() } private extension SwipeCreditCardViewController { - func applyCardWithSuccess(success: Bool) { + func applyCardWithSuccess(_ success: Bool) { let cardFullDigits = success ? "4242424242424242" : "4000000000000002" stripeManager.registerCard(cardFullDigits, month: 04, year: 2018, securityCode: "123", postalCode: "10013") @@ -144,11 +144,11 @@ private extension SwipeCreditCardViewController { .addDisposableTo(rx_disposeBag) } - @IBAction func dev_creditCardOKTapped(sender: AnyObject) { + @IBAction func dev_creditCardOKTapped(_ sender: AnyObject) { applyCardWithSuccess(true) } - @IBAction func dev_creditCardFailTapped(sender: AnyObject) { + @IBAction func dev_creditCardFailTapped(_ sender: AnyObject) { applyCardWithSuccess(false) } } diff --git a/Kiosk/Bid Fulfillment/YourBiddingDetailsViewController.swift b/Kiosk/Bid Fulfillment/YourBiddingDetailsViewController.swift index db4ad828..4df0532f 100644 --- a/Kiosk/Bid Fulfillment/YourBiddingDetailsViewController.swift +++ b/Kiosk/Bid Fulfillment/YourBiddingDetailsViewController.swift @@ -45,11 +45,11 @@ class YourBiddingDetailsViewController: UIViewController { .addDisposableTo(rx_disposeBag) } - @IBAction func confirmButtonTapped(sender: AnyObject) { + @IBAction func confirmButtonTapped(_ sender: AnyObject) { fulfillmentContainer()?.closeFulfillmentModal() } - class func instantiateFromStoryboard(storyboard: UIStoryboard) -> YourBiddingDetailsViewController { + class func instantiateFromStoryboard(_ storyboard: UIStoryboard) -> YourBiddingDetailsViewController { return storyboard.viewControllerWithID(.YourBidderDetails) as! YourBiddingDetailsViewController } } diff --git a/Kiosk/Help/HelpAnimator.swift b/Kiosk/Help/HelpAnimator.swift index a70cccad..7b156f5e 100644 --- a/Kiosk/Help/HelpAnimator.swift +++ b/Kiosk/Help/HelpAnimator.swift @@ -9,18 +9,18 @@ class HelpAnimator: NSObject, UIViewControllerAnimatedTransitioning { super.init() } - func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval { + func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return AnimationDuration.Normal } - func animateTransition(transitionContext: UIViewControllerContextTransitioning) { - let containerView = transitionContext.containerView()! + func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { + let containerView = transitionContext.containerView - let fromView:UIView! = transitionContext.viewForKey(UITransitionContextFromViewKey) ?? transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!.view - let toView:UIView! = transitionContext.viewForKey(UITransitionContextToViewKey) ?? transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!.view + let fromView:UIView! = transitionContext.view(forKey: UITransitionContextViewKey.from) ?? transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!.view + let toView:UIView! = transitionContext.view(forKey: UITransitionContextViewKey.to) ?? transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!.view if presenting { - let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)! as! HelpViewController + let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)! as! HelpViewController let dismissTapGestureRecognizer = UITapGestureRecognizer() dismissTapGestureRecognizer @@ -35,21 +35,21 @@ class HelpAnimator: NSObject, UIViewControllerAnimatedTransitioning { toViewController.dismissTapGestureRecognizer = dismissTapGestureRecognizer containerView.addGestureRecognizer(dismissTapGestureRecognizer) - fromView.userInteractionEnabled = false + fromView.isUserInteractionEnabled = false - containerView.backgroundColor = .blackColor() + containerView.backgroundColor = .black() containerView.addSubview(fromView) containerView.addSubview(toView) - toView.alignTop("0", bottom: "0", toView: containerView) + toView.alignTop("0", bottom: "0", to: containerView) toView.constrainWidth("\(HelpViewController.width)") - toViewController.positionConstraints = toView.alignAttribute(.Left, toAttribute: .Right, ofView: containerView, predicate: "0") as? [NSLayoutConstraint] + toViewController.positionConstraints = toView.alignAttribute(.left, to: .right, of: containerView, predicate: "0") as? [NSLayoutConstraint] containerView.layoutIfNeeded() - UIView.animateWithDuration(transitionDuration(transitionContext), animations: { + UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: { containerView.removeConstraints(toViewController.positionConstraints ?? []) - toViewController.positionConstraints = toView.alignLeading(nil, trailing: "0", toView: containerView) as? [NSLayoutConstraint] + toViewController.positionConstraints = toView.alignLeading(nil, trailing: "0", to: containerView) as? [NSLayoutConstraint] containerView.layoutIfNeeded() fromView.alpha = 0.5 @@ -57,27 +57,27 @@ class HelpAnimator: NSObject, UIViewControllerAnimatedTransitioning { transitionContext.completeTransition(true) }) } else { - let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)! as! HelpViewController + let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)! as! HelpViewController if let dismissTapGestureRecognizer = fromViewController.dismissTapGestureRecognizer { containerView.removeGestureRecognizer(dismissTapGestureRecognizer) } - toView.userInteractionEnabled = true + toView.isUserInteractionEnabled = true containerView.addSubview(toView) containerView.addSubview(fromView) - UIView.animateWithDuration(self.transitionDuration(transitionContext), animations: { + UIView.animate(withDuration: self.transitionDuration(using: transitionContext), animations: { containerView.removeConstraints(fromViewController.positionConstraints ?? []) - fromViewController.positionConstraints = fromView.alignAttribute(.Left, toAttribute: .Right, ofView: containerView, predicate: "0") as? [NSLayoutConstraint] + fromViewController.positionConstraints = fromView.alignAttribute(.left, to: .right, of: containerView, predicate: "0") as? [NSLayoutConstraint] containerView.layoutIfNeeded() toView.alpha = 1.0 }, completion: { (value: Bool) in transitionContext.completeTransition(true) // This following line is to work around a bug in iOS 8 💩 - UIApplication.sharedApplication().keyWindow!.insertSubview(transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!.view, atIndex: 0) + UIApplication.shared.keyWindow!.insertSubview(transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!.view, at: 0) }) } } diff --git a/Kiosk/Help/HelpViewController.swift b/Kiosk/Help/HelpViewController.swift index 0c63d96b..393f8541 100644 --- a/Kiosk/Help/HelpViewController.swift +++ b/Kiosk/Help/HelpViewController.swift @@ -10,14 +10,14 @@ class HelpViewController: UIViewController { var positionConstraints: [NSLayoutConstraint]? var dismissTapGestureRecognizer: UITapGestureRecognizer? - private let stackView = ORTagBasedAutoStackView() + fileprivate let stackView = ORTagBasedAutoStackView() - private var buyersPremiumButton: UIButton! + fileprivate var buyersPremiumButton: UIButton! - private let sideMargin: Float = 90.0 - private let topMargin: Float = 45.0 - private let headerMargin: Float = 25.0 - private let inbetweenMargin: Float = 10.0 + fileprivate let sideMargin: Float = 90.0 + fileprivate let topMargin: Float = 45.0 + fileprivate let headerMargin: Float = 25.0 + fileprivate let inbetweenMargin: Float = 10.0 var showBuyersPremiumCommand = { () -> CocoaAction in appDelegate().showBuyersPremiumCommand() @@ -58,7 +58,7 @@ class HelpViewController: UIViewController { super.viewDidLoad() // Configure view - view.backgroundColor = .whiteColor() + view.backgroundColor = .white() addSubviews() } @@ -67,49 +67,49 @@ class HelpViewController: UIViewController { private extension HelpViewController { enum SubviewTag: Int { - case AssistanceLabel = 0 - case StuckLabel, StuckExplainLabel - case BidLabel, BidExplainLabel - case RegisterButton - case BidderDetailsLabel, BidderDetailsExplainLabel, BidderDetailsButton - case ConditionsOfSaleButton, BuyersPremiumButton, PrivacyPolicyButton + case assistanceLabel = 0 + case stuckLabel, stuckExplainLabel + case bidLabel, bidExplainLabel + case registerButton + case bidderDetailsLabel, bidderDetailsExplainLabel, bidderDetailsButton + case conditionsOfSaleButton, buyersPremiumButton, privacyPolicyButton } func addSubviews() { // Configure subviews let assistanceLabel = ARSerifLabel() - assistanceLabel.font = assistanceLabel.font.fontWithSize(35) + assistanceLabel.font = assistanceLabel.font.withSize(35) assistanceLabel.text = "Assistance" - assistanceLabel.tag = SubviewTag.AssistanceLabel.rawValue + assistanceLabel.tag = SubviewTag.assistanceLabel.rawValue - let stuckLabel = titleLabel(.StuckLabel, title: "Stuck in the process?") + let stuckLabel = titleLabel(.stuckLabel, title: "Stuck in the process?") - let stuckExplainLabel = wrappingSerifLabel(.StuckExplainLabel, text: "Find the nearest Artsy representative and they will assist you.") + let stuckExplainLabel = wrappingSerifLabel(.stuckExplainLabel, text: "Find the nearest Artsy representative and they will assist you.") - let bidLabel = titleLabel(.BidLabel, title: "How do I place a bid?") + let bidLabel = titleLabel(.bidLabel, title: "How do I place a bid?") - let bidExplainLabel = wrappingSerifLabel(.BidExplainLabel, text: "Enter the amount you would like to bid. You will confirm this bid in the next step. Enter your mobile number or bidder number and PIN that you received when you registered.") + let bidExplainLabel = wrappingSerifLabel(.bidExplainLabel, text: "Enter the amount you would like to bid. You will confirm this bid in the next step. Enter your mobile number or bidder number and PIN that you received when you registered.") bidExplainLabel.makeSubstringsBold(["mobile number", "bidder number", "PIN"]) - let registerButton = blackButton(.RegisterButton, title: "Register") + let registerButton = blackButton(.registerButton, title: "Register") registerButton.rx_action = registerToBidCommand(connectedToInternetOrStubbing()) - let bidderDetailsLabel = titleLabel(.BidderDetailsLabel, title: "What Are Bidder Details?") + let bidderDetailsLabel = titleLabel(.bidderDetailsLabel, title: "What Are Bidder Details?") - let bidderDetailsExplainLabel = wrappingSerifLabel(.BidderDetailsExplainLabel, text: "The bidder number is how you can identify yourself to bid and see your place in bid history. The PIN is a four digit number that authenticates your bid.") + let bidderDetailsExplainLabel = wrappingSerifLabel(.bidderDetailsExplainLabel, text: "The bidder number is how you can identify yourself to bid and see your place in bid history. The PIN is a four digit number that authenticates your bid.") bidderDetailsExplainLabel.makeSubstringsBold(["bidder number", "PIN"]) - let sendDetailsButton = blackButton(.BidderDetailsButton, title: "Send me my details") + let sendDetailsButton = blackButton(.bidderDetailsButton, title: "Send me my details") sendDetailsButton.rx_action = requestBidderDetailsCommand(connectedToInternetOrStubbing()) - let conditionsButton = serifButton(.ConditionsOfSaleButton, title: "Conditions of Sale") + let conditionsButton = serifButton(.conditionsOfSaleButton, title: "Conditions of Sale") conditionsButton.rx_action = showConditionsOfSaleCommand() - buyersPremiumButton = serifButton(.BuyersPremiumButton, title: "Buyers Premium") + buyersPremiumButton = serifButton(.buyersPremiumButton, title: "Buyers Premium") buyersPremiumButton.rx_action = showBuyersPremiumCommand() - let privacyButton = serifButton(.PrivacyPolicyButton, title: "Privacy Policy") + let privacyButton = serifButton(.privacyPolicyButton, title: "Privacy Policy") privacyButton.rx_action = showPrivacyPolicyCommand() // Add subviews @@ -140,7 +140,7 @@ private extension HelpViewController { func blackButton(tag: SubviewTag, title: String) -> ARBlackFlatButton { let button = ARBlackFlatButton() - button.setTitle(title, forState: .Normal) + button.setTitle(title, for: UIControlState()) button.tag = tag.rawValue return button @@ -148,10 +148,10 @@ private extension HelpViewController { func serifButton(tag: SubviewTag, title: String) -> ARUnderlineButton { let button = ARUnderlineButton() - button.setTitle(title, forState: .Normal) - button.setTitleColor(.artsyHeavyGrey(), forState: .Normal) - button.titleLabel?.font = UIFont.serifFontWithSize(18) - button.contentHorizontalAlignment = .Left + button.setTitle(title, for: UIControlState()) + button.setTitleColor(.artsyHeavyGrey(), for: UIControlState()) + button.titleLabel?.font = UIFont.serifFont(withSize: 18) + button.contentHorizontalAlignment = .left button.tag = tag.rawValue return button @@ -159,8 +159,8 @@ private extension HelpViewController { func wrappingSerifLabel(tag: SubviewTag, text: String) -> UILabel { let label = ARSerifLabel() - label.font = label.font.fontWithSize(18) - label.lineBreakMode = .ByWordWrapping + label.font = label.font.withSize(18) + label.lineBreakMode = .byWordWrapping label.preferredMaxLayoutWidth = CGFloat(HelpViewController.width - sideMargin) label.tag = tag.rawValue @@ -174,7 +174,7 @@ private extension HelpViewController { func titleLabel(tag: SubviewTag, title: String) -> ARSerifLabel { let label = ARSerifLabel() - label.font = label.font.fontWithSize(24) + label.font = label.font.withSize(24) label.text = title label.tag = tag.rawValue return label diff --git a/Kiosk/HelperFunctions.swift b/Kiosk/HelperFunctions.swift index 5957a3bb..b69fe6fe 100644 --- a/Kiosk/HelperFunctions.swift +++ b/Kiosk/HelperFunctions.swift @@ -2,31 +2,31 @@ import Foundation // Collection of stanardised mapping funtions for Rx work -func stringIsEmailAddress(text: String) -> Bool { +func stringIsEmailAddress(_ text: String) -> Bool { let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}" let testPredicate = NSPredicate(format:"SELF MATCHES %@", emailRegex) - return testPredicate.evaluateWithObject(text) + return testPredicate.evaluate(with: text) } -func centsToPresentableDollarsString(cents: Int) -> String { - guard let dollars = NSNumberFormatter.currencyStringForCents(cents) else { +func centsToPresentableDollarsString(_ cents: Int) -> String { + guard let dollars = NumberFormatter.currencyString(forCents: cents as NSNumber!) else { return "" } return dollars } -func isZeroLengthString(string: String) -> Bool { +func isZeroLength(string: String) -> Bool { return string.isEmpty } -func isStringLengthIn(range: Range) -> (String) -> Bool { +func isStringLength(in range: Range) -> (String) -> Bool { return { string in return range.contains(string.characters.count) } } -func isStringOfLength(length: Int) -> (String) -> Bool { +func isStringOf(length: Int) -> (String) -> Bool { return { string in return string.characters.count == length } @@ -38,7 +38,7 @@ func isStringLengthAtLeast(length: Int) -> (String) -> Bool { } } -func isStringLengthOneOf(lengths: [Int]) -> (String) -> Bool { +func isStringLength(oneOf lengths: [Int]) -> (String) -> Bool { return { string in return lengths.contains(string.characters.count) } diff --git a/Kiosk/ListingsCollectionViewCell.swift b/Kiosk/ListingsCollectionViewCell.swift index 6c52c6ab..52826a10 100644 --- a/Kiosk/ListingsCollectionViewCell.swift +++ b/Kiosk/ListingsCollectionViewCell.swift @@ -5,8 +5,8 @@ import RxCocoa import NSObject_Rx class ListingsCollectionViewCell: UICollectionViewCell { - typealias DownloadImageClosure = (url: NSURL?, imageView: UIImageView) -> () - typealias CancelDownloadImageClosure = (imageView: UIImageView) -> () + typealias DownloadImageClosure = (_ url: URL?, _ imageView: UIImageView) -> () + typealias CancelDownloadImageClosure = (_ imageView: UIImageView) -> () dynamic let lotNumberLabel = ListingsCollectionViewCell._sansSerifLabel() dynamic let artworkImageView = ListingsCollectionViewCell._artworkImageView() @@ -26,32 +26,32 @@ class ListingsCollectionViewCell: UICollectionViewCell { return [self.imageGestureSigal, self.infoGesture].toObservable().merge() }() - private lazy var imageGestureSigal: Observable = { + fileprivate lazy var imageGestureSigal: Observable = { let recognizer = UITapGestureRecognizer() self.artworkImageView.addGestureRecognizer(recognizer) self.artworkImageView.userInteractionEnabled = true return recognizer.rx_event.map { _ in NSDate() } }() - private lazy var infoGesture: Observable = { + fileprivate lazy var infoGesture: Observable = { let recognizer = UITapGestureRecognizer() self.moreInfoLabel.addGestureRecognizer(recognizer) self.moreInfoLabel.userInteractionEnabled = true return recognizer.rx_event.map { _ in NSDate() } }() - private var _preparingForReuse = PublishSubject() + fileprivate var _preparingForReuse = PublishSubject() var preparingForReuse: Observable { return _preparingForReuse.asObservable() } var viewModel = PublishSubject() - func setViewModel(newViewModel: SaleArtworkViewModel) { + func setViewModel(_ newViewModel: SaleArtworkViewModel) { self.viewModel.onNext(newViewModel) } - private var _bidPressed = PublishSubject() + fileprivate var _bidPressed = PublishSubject() var bidPressed: Observable { return _bidPressed.asObservable() } @@ -70,7 +70,7 @@ class ListingsCollectionViewCell: UICollectionViewCell { override func prepareForReuse() { super.prepareForReuse() - cancelDownloadImage?(imageView: artworkImageView) + cancelDownloadImage?(artworkImageView) _preparingForReuse.onNext() setupSubscriptions() } @@ -93,7 +93,7 @@ class ListingsCollectionViewCell: UICollectionViewCell { .bindTo(lotNumberLabel.rx_text) .addDisposableTo(reuseBag) - viewModel.map { (viewModel) -> NSURL? in + viewModel.map { (viewModel) -> URL? in return viewModel.thumbnailURL }.subscribeNext { [weak self] url in guard let imageView = self?.artworkImageView else { return } @@ -133,7 +133,7 @@ class ListingsCollectionViewCell: UICollectionViewCell { .addDisposableTo(reuseBag) bidButton.rx_tap.subscribeNext { [weak self] in - self?._bidPressed.onNext(NSDate()) + self?._bidPressed.onNext(Date()) } .addDisposableTo(reuseBag) } @@ -150,54 +150,54 @@ private extension ListingsCollectionViewCell { class func _rightAlignedNormalLabel() -> UILabel { let label = _normalLabel() - label.textAlignment = .Right + label.textAlignment = .right label.numberOfLines = 1 return label } class func _normalLabel() -> UILabel { let label = ARSerifLabel() - label.font = label.font.fontWithSize(16) + label.font = label.font.withSize(16) label.numberOfLines = 1 return label } class func _sansSerifLabel() -> UILabel { let label = ARSansSerifLabel() - label.font = label.font.fontWithSize(12) + label.font = label.font.withSize(12) label.numberOfLines = 1 return label } class func _italicsLabel() -> UILabel { let label = ARItalicsSerifLabel() - label.font = label.font.fontWithSize(16) + label.font = label.font.withSize(16) label.numberOfLines = 1 return label } class func _largeLabel() -> UILabel { let label = _normalLabel() - label.font = label.font.fontWithSize(20) + label.font = label.font.withSize(20) return label } class func _bidButton() -> ActionButton { let button = ActionButton() - button.setTitle("BID", forState: .Normal) + button.setTitle("BID", for: UIControlState()) return button } class func _boldLabel() -> UILabel { let label = _normalLabel() - label.font = UIFont.serifBoldFontWithSize(label.font.pointSize) + label.font = UIFont.serifBoldFont(withSize: label.font.pointSize) label.numberOfLines = 1 return label } class func _infoLabel() -> UILabel { let label = ARSansSerifLabelWithChevron() - label.tintColor = .blackColor() + label.tintColor = .black() label.text = "MORE INFO" return label } diff --git a/Kiosk/Observable+JSONAble.swift b/Kiosk/Observable+JSONAble.swift index 940982ba..feff5d61 100644 --- a/Kiosk/Observable+JSONAble.swift +++ b/Kiosk/Observable+JSONAble.swift @@ -8,14 +8,14 @@ enum EidolonError: String { case MissingData } -extension EidolonError: ErrorType { } +extension EidolonError: Error { } extension Observable { typealias Dictionary = [String: AnyObject] /// Get given JSONified data, pass back objects - func mapToObject(classType: B.Type) -> Observable { + func mapTo(object classType: B.Type) -> Observable { return self.map { json in guard let dict = json as? Dictionary else { throw EidolonError.CouldNotParseJSON @@ -26,7 +26,7 @@ extension Observable { } /// Get given JSONified data, pass back objects as an array - func mapToObjectArray(classType: B.Type) -> Observable<[B]> { + func mapTo(arrayOf classType: B.Type) -> Observable<[B]> { return self.map { json in guard let array = json as? [AnyObject] else { throw EidolonError.CouldNotParseJSON diff --git a/Kiosk/Observable+Logging.swift b/Kiosk/Observable+Logging.swift index 590c39e6..893500b6 100644 --- a/Kiosk/Observable+Logging.swift +++ b/Kiosk/Observable+Logging.swift @@ -32,4 +32,4 @@ extension Observable { } } } -} \ No newline at end of file +} diff --git a/Kiosk/Observable+Operators.swift b/Kiosk/Observable+Operators.swift index 35101417..240a1da4 100644 --- a/Kiosk/Observable+Operators.swift +++ b/Kiosk/Observable+Operators.swift @@ -20,7 +20,7 @@ extension Observable { // // Still not sure if this is a good idea. - func flatMapTo(selector: Element -> () -> Observable) -> Observable { + func flatMapTo(_ selector: (Element) -> () -> Observable) -> Observable { return self.map { (s) -> Observable in return selector(s)() }.switchLatest() @@ -50,7 +50,7 @@ extension Observable where Element: OptionalType { } } - func replaceNilWith(nilValue: Element.Wrapped) -> Observable { + func replaceNil(with nilValue: Element.Wrapped) -> Observable { return flatMap { (element) -> Observable in if let value = element.value { return .just(value) @@ -61,8 +61,9 @@ extension Observable where Element: OptionalType { } } +// TODO: Added in new RxSwift? extension Observable { - func doOnNext(closure: Element -> Void) -> Observable { + func doOnNext(_ closure: (Element) -> Void) -> Observable { return doOn { (event: Event) in switch event { case .Next(let value): @@ -72,7 +73,7 @@ extension Observable { } } - func doOnCompleted(closure: () -> Void) -> Observable { + func doOnCompleted(_ closure: () -> Void) -> Observable { return doOn { (event: Event) in switch event { case .Completed: @@ -82,7 +83,7 @@ extension Observable { } } - func doOnError(closure: ErrorType -> Void) -> Observable { + func doOnError(_ closure: (ErrorType) -> Void) -> Observable { return doOn { (event: Event) in switch event { case .Error(let error): @@ -96,7 +97,7 @@ extension Observable { private let backgroundScheduler = SerialDispatchQueueScheduler(globalConcurrentQueueQOS: .Default) extension Observable { - func mapReplace(value: T) -> Observable { + func mapReplace(with value: T) -> Observable { return map { _ -> T in return value } @@ -116,7 +117,7 @@ extension Observable where Element: BooleanType { } } -extension CollectionType where Generator.Element: ObservableType, Generator.Element.E: BooleanType { +extension Collection where Iterator.Element: ObservableType, Iterator.Element.E: Bool { func combineLatestAnd() -> Observable { return combineLatest { bools -> Bool in @@ -137,11 +138,11 @@ extension CollectionType where Generator.Element: ObservableType, Generator.Elem extension ObservableType { - func then(closure: () -> Observable?) -> Observable { + func then(_ closure: () -> Observable?) -> Observable { return then(closure() ?? .empty()) } - func then(@autoclosure(escaping) closure: () -> Observable) -> Observable { + func then(@autoclosure(escaping) _ closure: () -> Observable) -> Observable { let next = Observable.deferred { return closure() ?? .empty() } @@ -158,7 +159,7 @@ extension Observable { } } -func sendDispatchCompleted(observer: AnyObserver) { +func sendDispatchCompleted(to observer: AnyObserver) { dispatch_async(dispatch_get_main_queue()) { observer.onCompleted() } diff --git a/Kiosk/Sale Artwork Details/ImageTiledDataSource.swift b/Kiosk/Sale Artwork Details/ImageTiledDataSource.swift index 6951157c..7f53f24f 100644 --- a/Kiosk/Sale Artwork Details/ImageTiledDataSource.swift +++ b/Kiosk/Sale Artwork Details/ImageTiledDataSource.swift @@ -9,11 +9,11 @@ class TiledImageDataSourceWithImage: ARWebTiledImageDataSource { super.init() tileFormat = "jpg"; - tileBaseURL = NSURL(string: image.baseURL) + tileBaseURL = URL(string: image.baseURL) tileSize = image.tileSize maxTiledHeight = image.maxTiledHeight maxTiledWidth = image.maxTiledWidth maxTileLevel = image.maxLevel minTileLevel = 11; } -} \ No newline at end of file +} diff --git a/Kiosk/Sale Artwork Details/SaleArtworkDetailsViewController.swift b/Kiosk/Sale Artwork Details/SaleArtworkDetailsViewController.swift index a469c012..6b4ed16a 100644 --- a/Kiosk/Sale Artwork Details/SaleArtworkDetailsViewController.swift +++ b/Kiosk/Sale Artwork Details/SaleArtworkDetailsViewController.swift @@ -17,7 +17,7 @@ class SaleArtworkDetailsViewController: UIViewController { appDelegate().showBuyersPremiumCommand() } - class func instantiateFromStoryboard(storyboard: UIStoryboard) -> SaleArtworkDetailsViewController { + class func instantiateFromStoryboard(_ storyboard: UIStoryboard) -> SaleArtworkDetailsViewController { return storyboard.viewControllerWithID(.SaleArtworkDetail) as! SaleArtworkDetailsViewController } @@ -48,71 +48,71 @@ class SaleArtworkDetailsViewController: UIViewController { // works if we defer recalculating their geometry to the next runloop. // This wasn't an issue with RAC's rac_signalForSelector because that invoked the signal _after_ this method completed. // So that's what I've done here. - dispatch_async(dispatch_get_main_queue()) { + DispatchQueue.main.async { self.layoutSubviews.onNext() } } - override func viewWillAppear(animated: Bool) { + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) viewWillAppear.onCompleted() } - override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue == .ZoomIntoArtwork { - let nextViewController = segue.destinationViewController as! SaleArtworkZoomViewController + let nextViewController = segue.destination as! SaleArtworkZoomViewController nextViewController.saleArtwork = saleArtwork } } enum MetadataStackViewTag: Int { - case LotNumberLabel = 1 - case ArtistNameLabel - case ArtworkNameLabel - case ArtworkMediumLabel - case ArtworkDimensionsLabel - case ImageRightsLabel - case EstimateTopBorder - case EstimateLabel - case EstimateBottomBorder - case CurrentBidLabel - case CurrentBidValueLabel - case NumberOfBidsPlacedLabel - case BidButton - case BuyersPremium + case lotNumberLabel = 1 + case artistNameLabel + case artworkNameLabel + case artworkMediumLabel + case artworkDimensionsLabel + case imageRightsLabel + case estimateTopBorder + case estimateLabel + case estimateBottomBorder + case currentBidLabel + case currentBidValueLabel + case numberOfBidsPlacedLabel + case bidButton + case buyersPremium } - @IBAction func backWasPressed(sender: AnyObject) { - navigationController?.popViewControllerAnimated(true) + @IBAction func backWasPressed(_ sender: AnyObject) { + navigationController?.popViewController(animated: true) } - private func setupMetadataView() { + fileprivate func setupMetadataView() { enum LabelType { - case Serif - case SansSerif - case ItalicsSerif - case Bold + case serif + case sansSerif + case italicsSerif + case bold } - func label(type: LabelType, tag: MetadataStackViewTag, fontSize: CGFloat = 16.0) -> UILabel { + func label(_ type: LabelType, tag: MetadataStackViewTag, fontSize: CGFloat = 16.0) -> UILabel { let label: UILabel = { () -> UILabel in switch type { - case .Serif: + case .serif: return ARSerifLabel() - case .SansSerif: + case .sansSerif: return ARSansSerifLabel() - case .ItalicsSerif: + case .italicsSerif: return ARItalicsSerifLabel() - case .Bold: + case .bold: let label = ARSerifLabel() - label.font = UIFont.sansSerifFontWithSize(label.font.pointSize) + label.font = UIFont.sansSerifFont(withSize: label.font.pointSize) return label } }() - label.lineBreakMode = .ByWordWrapping - label.font = label.font.fontWithSize(fontSize) + label.lineBreakMode = .byWordWrapping + label.font = label.font.withSize(fontSize) label.tag = tag.rawValue label.preferredMaxLayoutWidth = 276 @@ -122,8 +122,8 @@ class SaleArtworkDetailsViewController: UIViewController { let hasLotNumber = (saleArtwork.lotNumber != nil) if let _ = saleArtwork.lotNumber { - let lotNumberLabel = label(.SansSerif, tag: .LotNumberLabel) - lotNumberLabel.font = lotNumberLabel.font.fontWithSize(12) + let lotNumberLabel = label(.sansSerif, tag: .lotNumberLabel) + lotNumberLabel.font = lotNumberLabel.font.withSize(12) metadataStackView.addSubview(lotNumberLabel, withTopMargin: "0", sideMargin: "0") saleArtwork @@ -135,26 +135,26 @@ class SaleArtworkDetailsViewController: UIViewController { } if let artist = artist() { - let artistNameLabel = label(.SansSerif, tag: .ArtistNameLabel) + let artistNameLabel = label(.sansSerif, tag: .artistNameLabel) artistNameLabel.text = artist.name metadataStackView.addSubview(artistNameLabel, withTopMargin: hasLotNumber ? "10" : "0", sideMargin: "0") } - let artworkNameLabel = label(.ItalicsSerif, tag: .ArtworkNameLabel) + let artworkNameLabel = label(.italicsSerif, tag: .artworkNameLabel) artworkNameLabel.text = "\(saleArtwork.artwork.title), \(saleArtwork.artwork.date)" metadataStackView.addSubview(artworkNameLabel, withTopMargin: "10", sideMargin: "0") if let medium = saleArtwork.artwork.medium { if medium.isNotEmpty { - let mediumLabel = label(.Serif, tag: .ArtworkMediumLabel) + let mediumLabel = label(.serif, tag: .artworkMediumLabel) mediumLabel.text = medium metadataStackView.addSubview(mediumLabel, withTopMargin: "22", sideMargin: "0") } } if saleArtwork.artwork.dimensions.count > 0 { - let dimensionsLabel = label(.Serif, tag: .ArtworkDimensionsLabel) - dimensionsLabel.text = (saleArtwork.artwork.dimensions as NSArray).componentsJoinedByString("\n") + let dimensionsLabel = label(.serif, tag: .artworkDimensionsLabel) + dimensionsLabel.text = (saleArtwork.artwork.dimensions as NSArray).componentsJoined(by: "\n") metadataStackView.addSubview(dimensionsLabel, withTopMargin: "5", sideMargin: "0") } @@ -170,16 +170,16 @@ class SaleArtworkDetailsViewController: UIViewController { let estimateTopBorder = UIView() estimateTopBorder.constrainHeight("1") - estimateTopBorder.tag = MetadataStackViewTag.EstimateTopBorder.rawValue + estimateTopBorder.tag = MetadataStackViewTag.estimateTopBorder.rawValue metadataStackView.addSubview(estimateTopBorder, withTopMargin: "22", sideMargin: "0") - let estimateLabel = label(.Serif, tag: .EstimateLabel) + let estimateLabel = label(.serif, tag: .estimateLabel) estimateLabel.text = saleArtwork.viewModel.estimateString metadataStackView.addSubview(estimateLabel, withTopMargin: "15", sideMargin: "0") let estimateBottomBorder = UIView() estimateBottomBorder.constrainHeight("1") - estimateBottomBorder.tag = MetadataStackViewTag.EstimateBottomBorder.rawValue + estimateBottomBorder.tag = MetadataStackViewTag.estimateBottomBorder.rawValue metadataStackView.addSubview(estimateBottomBorder, withTopMargin: "10", sideMargin: "0") viewWillAppear @@ -196,7 +196,7 @@ class SaleArtworkDetailsViewController: UIViewController { return (cents as Int ?? 0) > 0 } - let currentBidLabel = label(.Serif, tag: .CurrentBidLabel) + let currentBidLabel = label(.serif, tag: .currentBidLabel) hasBids .flatMap { hasBids -> Observable in @@ -211,7 +211,7 @@ class SaleArtworkDetailsViewController: UIViewController { metadataStackView.addSubview(currentBidLabel, withTopMargin: "22", sideMargin: "0") - let currentBidValueLabel = label(.Bold, tag: .CurrentBidValueLabel, fontSize: 27) + let currentBidValueLabel = label(.bold, tag: .currentBidValueLabel, fontSize: 27) saleArtwork .viewModel .currentBid() @@ -219,7 +219,7 @@ class SaleArtworkDetailsViewController: UIViewController { .addDisposableTo(rx_disposeBag) metadataStackView.addSubview(currentBidValueLabel, withTopMargin: "10", sideMargin: "0") - let numberOfBidsPlacedLabel = label(.Serif, tag: .NumberOfBidsPlacedLabel) + let numberOfBidsPlacedLabel = label(.serif, tag: .numberOfBidsPlacedLabel) saleArtwork .viewModel .numberOfBidsWithReserve @@ -255,34 +255,34 @@ class SaleArtworkDetailsViewController: UIViewController { .bindTo(bidButton.rx_enabled) .addDisposableTo(rx_disposeBag) - bidButton.tag = MetadataStackViewTag.BidButton.rawValue + bidButton.tag = MetadataStackViewTag.bidButton.rawValue metadataStackView.addSubview(bidButton, withTopMargin: "40", sideMargin: "0") if let _ = buyersPremium() { let buyersPremiumView = UIView() - buyersPremiumView.tag = MetadataStackViewTag.BuyersPremium.rawValue + buyersPremiumView.tag = MetadataStackViewTag.buyersPremium.rawValue let buyersPremiumLabel = ARSerifLabel() - buyersPremiumLabel.font = buyersPremiumLabel.font.fontWithSize(16) + buyersPremiumLabel.font = buyersPremiumLabel.font.withSize(16) buyersPremiumLabel.text = "This work has a " buyersPremiumLabel.textColor = .artsyHeavyGrey() let buyersPremiumButton = ARButton() let title = "buyers premium" - let attributes: [String: AnyObject] = [ NSUnderlineStyleAttributeName: NSUnderlineStyle.StyleSingle.rawValue, NSFontAttributeName: buyersPremiumLabel.font ]; + let attributes: [String: AnyObject] = [ NSUnderlineStyleAttributeName: NSUnderlineStyle.styleSingle.rawValue as AnyObject, NSFontAttributeName: buyersPremiumLabel.font ]; let attributedTitle = NSAttributedString(string: title, attributes: attributes) - buyersPremiumButton.setTitle(title, forState: .Normal) + buyersPremiumButton.setTitle(title, for: UIControlState()) buyersPremiumButton.titleLabel?.attributedText = attributedTitle; - buyersPremiumButton.setTitleColor(.artsyHeavyGrey(), forState: .Normal) + buyersPremiumButton.setTitleColor(.artsyHeavyGrey(), for: UIControlState()) buyersPremiumButton.rx_action = showBuyersPremiumCommand() buyersPremiumView.addSubview(buyersPremiumLabel) buyersPremiumView.addSubview(buyersPremiumButton) - buyersPremiumLabel.alignTop("0", leading: "0", bottom: "0", trailing: nil, toView: buyersPremiumView) - buyersPremiumLabel.alignBaselineWithView(buyersPremiumButton, predicate: nil) - buyersPremiumButton.alignAttribute(.Left, toAttribute: .Right, ofView: buyersPremiumLabel, predicate: "0") + buyersPremiumLabel.alignTop("0", leading: "0", bottom: "0", trailing: nil, to: buyersPremiumView) + buyersPremiumLabel.alignBaseline(with: buyersPremiumButton, predicate: nil) + buyersPremiumButton.alignAttribute(.left, to: .right, of: buyersPremiumLabel, predicate: "0") metadataStackView.addSubview(buyersPremiumView, withTopMargin: "30", sideMargin: "0") } @@ -290,20 +290,20 @@ class SaleArtworkDetailsViewController: UIViewController { metadataStackView.bottomMarginHeight = CGFloat(NSNotFound) } - private func setupImageView(imageView: UIImageView) { + fileprivate func setupImageView(_ imageView: UIImageView) { if let image = saleArtwork.artwork.defaultImage { // We'll try to retrieve the thumbnail image from the cache. If we don't have it, we'll set the background colour to grey to indicate that we're downloading it. - let key = SDWebImageManager.sharedManager().cacheKeyForURL(image.thumbnailURL()) - let thumbnailImage = SDImageCache.sharedImageCache().imageFromDiskCacheForKey(key) + let key = SDWebImageManager.shared().cacheKey(for: image.thumbnailURL() as URL!) + let thumbnailImage = SDImageCache.shared().imageFromDiskCache(forKey: key) if thumbnailImage == nil { imageView.backgroundColor = .artsyLightGrey() } - imageView.sd_setImageWithURL(image.fullsizeURL(), placeholderImage: thumbnailImage) { (image, _, _, _) in + imageView.sd_setImage(with: image.fullsizeURL(), placeholderImage: thumbnailImage) { (image, _, _, _) in // If the image was successfully downloaded, make sure we aren't still displaying grey. if image != nil { - imageView.backgroundColor = .clearColor() + imageView.backgroundColor = .clear() } } @@ -317,8 +317,8 @@ class SaleArtworkDetailsViewController: UIViewController { }() imageView.constrainHeight( "\(heightConstraintNumber)" ) - imageView.contentMode = .ScaleAspectFit - imageView.userInteractionEnabled = true + imageView.contentMode = .scaleAspectFit + imageView.isUserInteractionEnabled = true let recognizer = UITapGestureRecognizer() imageView.addGestureRecognizer(recognizer) @@ -332,24 +332,24 @@ class SaleArtworkDetailsViewController: UIViewController { } } - private func setupAdditionalDetailStackView() { + fileprivate func setupAdditionalDetailStackView() { enum LabelType { - case Header - case Body + case header + case body } - func label(type: LabelType, layout: Observable? = nil) -> UILabel { + func label(_ type: LabelType, layout: Observable? = nil) -> UILabel { let (label, fontSize) = { () -> (UILabel, CGFloat) in switch type { - case .Header: + case .header: return (ARSansSerifLabel(), 14) - case .Body: + case .body: return (ARSerifLabel(), 16) } }() - label.font = label.font.fontWithSize(fontSize) - label.lineBreakMode = .ByWordWrapping + label.font = label.font.withSize(fontSize) + label.lineBreakMode = .byWordWrapping layout? .take(1) @@ -413,11 +413,11 @@ class SaleArtworkDetailsViewController: UIViewController { } } - private func artist() -> Artist? { + fileprivate func artist() -> Artist? { return saleArtwork.artwork.artists?.first } - private func retrieveImageRights() -> Observable { + fileprivate func retrieveImageRights() -> Observable { let artwork = saleArtwork.artwork if let imageRights = artwork.imageRights { @@ -434,7 +434,7 @@ class SaleArtworkDetailsViewController: UIViewController { } } - private func retrieveAdditionalInfo() -> Observable { + fileprivate func retrieveAdditionalInfo() -> Observable { let artwork = saleArtwork.artwork if let additionalInfo = artwork.additionalInfo { @@ -450,7 +450,7 @@ class SaleArtworkDetailsViewController: UIViewController { } } - private func retrieveArtistBlurb() -> Observable { + fileprivate func retrieveArtistBlurb() -> Observable { guard let artist = artist() else { return .empty() } diff --git a/Kiosk/Sale Artwork Details/SaleArtworkZoomViewController.swift b/Kiosk/Sale Artwork Details/SaleArtworkZoomViewController.swift index e5b6bf14..adf75063 100644 --- a/Kiosk/Sale Artwork Details/SaleArtworkZoomViewController.swift +++ b/Kiosk/Sale Artwork Details/SaleArtworkZoomViewController.swift @@ -16,21 +16,21 @@ class SaleArtworkZoomViewController: UIViewController { tiledView.decelerationRate = UIScrollViewDecelerationRateFast tiledView.showsHorizontalScrollIndicator = false tiledView.showsVerticalScrollIndicator = false - tiledView.contentMode = .ScaleAspectFit + tiledView.contentMode = .scaleAspectFit tiledView.dataSource = dataSource - tiledView.backgroundImageURL = image.fullsizeURL() + tiledView.backgroundImageURL = image.fullsizeURL() as URL! - view.insertSubview(tiledView, atIndex:0) + view.insertSubview(tiledView, at:0) tiledImageView = tiledView } - override func viewWillAppear(animated: Bool) { + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - tiledImageView.zoomToFit(false) + tiledImageView.zoom(toFit: false) } - @IBAction func backButtonTapped(sender: AnyObject) { - self.navigationController?.popViewControllerAnimated(true) + @IBAction func backButtonTapped(_ sender: AnyObject) { + self.navigationController?.popViewController(animated: true) } } diff --git a/Kiosk/Sale Artwork Details/WhitespaceGobbler.swift b/Kiosk/Sale Artwork Details/WhitespaceGobbler.swift index f83c1d43..490d9797 100644 --- a/Kiosk/Sale Artwork Details/WhitespaceGobbler.swift +++ b/Kiosk/Sale Artwork Details/WhitespaceGobbler.swift @@ -12,12 +12,12 @@ class WhitespaceGobbler: UIView { convenience init() { self.init(frame: CGRect.zero) - setContentHuggingPriority(50, forAxis: .Vertical) - setContentHuggingPriority(50, forAxis: .Horizontal) - backgroundColor = .clearColor() + setContentHuggingPriority(50, for: .vertical) + setContentHuggingPriority(50, for: .horizontal) + backgroundColor = .clear() } - override func intrinsicContentSize() -> CGSize { + override var intrinsicContentSize : CGSize { return CGSize.zero } } diff --git a/Kiosk/Storyboards/UIStoryboardExtensions.swift b/Kiosk/Storyboards/UIStoryboardExtensions.swift index c9326bd2..5db791db 100644 --- a/Kiosk/Storyboards/UIStoryboardExtensions.swift +++ b/Kiosk/Storyboards/UIStoryboardExtensions.swift @@ -10,7 +10,7 @@ extension UIStoryboard { return UIStoryboard(name: StoryboardNames.Fulfillment.rawValue, bundle: nil) } - func viewControllerWithID(identifier:ViewControllerStoryboardIdentifier) -> UIViewController { - return self.instantiateViewControllerWithIdentifier(identifier.rawValue) + func viewController(withID identifier:ViewControllerStoryboardIdentifier) -> UIViewController { + return self.instantiateViewController(withIdentifier: identifier.rawValue) } } diff --git a/Kiosk/UILabel+Fonts.swift b/Kiosk/UILabel+Fonts.swift index dff6100d..c6916576 100644 --- a/Kiosk/UILabel+Fonts.swift +++ b/Kiosk/UILabel+Fonts.swift @@ -8,9 +8,9 @@ extension UILabel { func makeSubstringBold(text: String) { let attributedText = self.attributedText!.mutableCopy() as! NSMutableAttributedString - let range: NSRange! = (self.text ?? NSString()).rangeOfString(text) + let range: NSRange! = (self.text ?? NSString() as String).range(of: text) if range.location != NSNotFound { - attributedText.setAttributes([NSFontAttributeName: UIFont.serifSemiBoldFontWithSize(self.font.pointSize)], range: range) + attributedText.setAttributes([NSFontAttributeName: UIFont.serifSemiBoldFont(withSize: self.font.pointSize)], range: range) } self.attributedText = attributedText @@ -23,15 +23,15 @@ extension UILabel { func makeSubstringItalic(text: String) { let attributedText = self.attributedText!.mutableCopy() as! NSMutableAttributedString - let range: NSRange! = (self.text ?? NSString()).rangeOfString(text) + let range: NSRange! = (self.text ?? NSString() as String).range(of: text) if range.location != NSNotFound { - attributedText.setAttributes([NSFontAttributeName: UIFont.serifItalicFontWithSize(self.font.pointSize)], range: range) + attributedText.setAttributes([NSFontAttributeName: UIFont.serifItalicFont(withSize: self.font.pointSize)], range: range) } self.attributedText = attributedText } - func setLineHeight(lineHeight: Int) { + func setLineHeight(_ lineHeight: Int) { let displayText = text ?? "" let attributedString = NSMutableAttributedString(string: displayText) let paragraphStyle = NSMutableParagraphStyle() @@ -43,7 +43,7 @@ extension UILabel { } func makeTransparent() { - opaque = false - backgroundColor = .clearColor() + isOpaque = false + backgroundColor = .clear() } } diff --git a/Kiosk/UIView+LongPressDisplayMessage.swift b/Kiosk/UIView+LongPressDisplayMessage.swift index 863ce2ca..81ffe94e 100644 --- a/Kiosk/UIView+LongPressDisplayMessage.swift +++ b/Kiosk/UIView+LongPressDisplayMessage.swift @@ -1,18 +1,18 @@ import UIKit import RxSwift -private func alertController(message: String, title: String) -> UIAlertController { - let alertController = UIAlertController(title: title, message: message, preferredStyle: .Alert) +private func alertController(_ message: String, title: String) -> UIAlertController { + let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) - alertController.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil)) + alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) return alertController } extension UIView { - typealias PresentAlertClosure = (alertController: UIAlertController) -> Void + typealias PresentAlertClosure = (_ alertController: UIAlertController) -> Void - func presentOnLongPress(message: String, title: String, closure: PresentAlertClosure) { + func presentOnLongPress(_ message: String, title: String, closure: @escaping PresentAlertClosure) { let recognizer = UILongPressGestureRecognizer() recognizer @@ -22,7 +22,7 @@ extension UIView { } .addDisposableTo(rx_disposeBag) - userInteractionEnabled = true + isUserInteractionEnabled = true addGestureRecognizer(recognizer) } } diff --git a/Kiosk/UIViewController+Bidding.swift b/Kiosk/UIViewController+Bidding.swift index f6fdf553..d76f1934 100644 --- a/Kiosk/UIViewController+Bidding.swift +++ b/Kiosk/UIViewController+Bidding.swift @@ -16,8 +16,8 @@ extension UIViewController { } // Present the VC, then once it's ready trigger it's own showing animations - appDelegate().appViewController.presentViewController(containerController, animated: false) { + appDelegate().appViewController.present(containerController, animated: false) { containerController.viewDidAppearAnimation(containerController.allowAnimations) } } -} \ No newline at end of file +} diff --git a/KioskTests/App/SwiftExtensionsTests.swift b/KioskTests/App/SwiftExtensionsTests.swift index 8f033d31..6697b246 100644 --- a/KioskTests/App/SwiftExtensionsTests.swift +++ b/KioskTests/App/SwiftExtensionsTests.swift @@ -27,7 +27,7 @@ class AnyOccupiable: NSObject, Occupiable { class SwiftExtensionsConfiguration: QuickConfiguration { - override class func configure(configuration: Configuration) { + override class func configure(_ configuration: Configuration) { sharedExamples("an Occupiable") { (sharedExampleContext: SharedExampleContext) in var empty: AnyOccupiable? var nonEmpty: AnyOccupiable? diff --git a/KioskTests/ArtsyAPISpec.swift b/KioskTests/ArtsyAPISpec.swift index 7286c139..75f5114f 100644 --- a/KioskTests/ArtsyAPISpec.swift +++ b/KioskTests/ArtsyAPISpec.swift @@ -16,7 +16,7 @@ func beInTheFuture() -> MatcherFunc { class ArtsyAPISpec: QuickSpec { override func spec() { - var defaults: NSUserDefaults! + var defaults: UserDefaults! var disposeBag: DisposeBag! var networking: Networking! diff --git a/KioskTests/Bid Fulfillment/AdminCCBypassNetworkModelTests.swift b/KioskTests/Bid Fulfillment/AdminCCBypassNetworkModelTests.swift index 6ed39697..9cd8f681 100644 --- a/KioskTests/Bid Fulfillment/AdminCCBypassNetworkModelTests.swift +++ b/KioskTests/Bid Fulfillment/AdminCCBypassNetworkModelTests.swift @@ -71,7 +71,7 @@ class AdminCCBypassNetworkModelTests: QuickSpec { } } -private func networkingForBidderCreatedByAdmin(createdByAdmin: Bool?) -> AuthorizedNetworking { +private func networkingForBidder(createdByAdmin: Bool?) -> AuthorizedNetworking { let sampleData: NSData if let createdByAdmin = createdByAdmin { diff --git a/KioskTests/Bid Fulfillment/PlaceBidViewControllerTests.swift b/KioskTests/Bid Fulfillment/PlaceBidViewControllerTests.swift index e3122272..59628ff1 100644 --- a/KioskTests/Bid Fulfillment/PlaceBidViewControllerTests.swift +++ b/KioskTests/Bid Fulfillment/PlaceBidViewControllerTests.swift @@ -44,11 +44,11 @@ class PlaceBidViewControllerTests: QuickSpec { override func spec() { var subject: PlaceBidViewController! let artworkJSON: [String: AnyObject] = [ - "id":"artwork_id", - "title" : "The Artwork Title", - "date": "23rd Nov", - "blurb" : "Something about the artwork", - "price": "$33,990", + "id":"artwork_id" as AnyObject, + "title" : "The Artwork Title" as AnyObject, + "date": "23rd Nov" as AnyObject, + "blurb" : "Something about the artwork" as AnyObject, + "price": "$33,990" as AnyObject, "artist": ["id": "artist_id", "name": "Artist Name"] ] diff --git a/KioskTests/Bid Fulfillment/RegistrationPasswordViewControllerTests.swift b/KioskTests/Bid Fulfillment/RegistrationPasswordViewControllerTests.swift index 755280f7..eeb9faab 100644 --- a/KioskTests/Bid Fulfillment/RegistrationPasswordViewControllerTests.swift +++ b/KioskTests/Bid Fulfillment/RegistrationPasswordViewControllerTests.swift @@ -9,7 +9,7 @@ import Moya class RegistrationPasswordViewControllerTests: QuickSpec { - func testSubject(emailExists emailExists: Bool = false) -> RegistrationPasswordViewController { + func testSubject(emailExists: Bool = false) -> RegistrationPasswordViewController { class TestViewModel: RegistrationPasswordViewModelType { diff --git a/KioskTests/Bid Fulfillment/RegistrationPasswordViewModelTests.swift b/KioskTests/Bid Fulfillment/RegistrationPasswordViewModelTests.swift index fcede8bb..494d5656 100644 --- a/KioskTests/Bid Fulfillment/RegistrationPasswordViewModelTests.swift +++ b/KioskTests/Bid Fulfillment/RegistrationPasswordViewModelTests.swift @@ -13,8 +13,8 @@ class RegistrationPasswordViewModelTests: QuickSpec { typealias Check = (() -> ())? - @warn_unused_result - func stubProvider(emailExists emailExists: Bool, emailCheck: Check, loginSucceeds: Bool, loginCheck: Check, passwordRequestSucceeds: Bool, passwordCheck: Check) -> Networking { + + func stubProvider(emailExists: Bool, emailCheck: Check, loginSucceeds: Bool, loginCheck: Check, passwordRequestSucceeds: Bool, passwordCheck: Check) -> Networking { let endpointsClosure = { (target: ArtsyAPI) -> Endpoint in diff --git a/KioskTests/Bid Fulfillment/StripeManagerTests.swift b/KioskTests/Bid Fulfillment/StripeManagerTests.swift index e810a8ac..62c5be78 100644 --- a/KioskTests/Bid Fulfillment/StripeManagerTests.swift +++ b/KioskTests/Bid Fulfillment/StripeManagerTests.swift @@ -74,7 +74,7 @@ class TestSTPAPIClient: STPAPIClient { ] ]) - override func createTokenWithCard(card: STPCard!, completion: STPCompletionBlock!) { + override func createToken(with card: STPCard!, completion: STPCompletionBlock!) { if succeed { completion(token, nil) } else { diff --git a/KioskTests/HelpViewControllerTests.swift b/KioskTests/HelpViewControllerTests.swift index 26c899ae..5adb8d7d 100644 --- a/KioskTests/HelpViewControllerTests.swift +++ b/KioskTests/HelpViewControllerTests.swift @@ -6,7 +6,7 @@ import RxSwift import Kiosk class HelpViewControllerConfiguration: QuickConfiguration { - override class func configure(configuration: Configuration) { + override class func configure(_ configuration: Configuration) { sharedExamples("a help view controller") { (sharedExampleContext: SharedExampleContext) in var subject: HelpViewController! @@ -43,4 +43,4 @@ class HelpViewControllerTests: QuickSpec { itBehavesLike("a help view controller") { ["subject": subject] } } } -} \ No newline at end of file +} diff --git a/KioskTests/ListingsViewControllerTests.swift b/KioskTests/ListingsViewControllerTests.swift index 6b186746..e382e9a8 100644 --- a/KioskTests/ListingsViewControllerTests.swift +++ b/KioskTests/ListingsViewControllerTests.swift @@ -8,7 +8,7 @@ import Foundation import Moya class ListingsViewControllerConfiguration: QuickConfiguration { - override class func configure(configuration: Configuration) { + override class func configure(_ configuration: Configuration) { sharedExamples("a listings controller") { (sharedExampleContext: SharedExampleContext) in var subject: ListingsViewController! var viewModel: ListingsViewControllerTestsStubbedViewModel! @@ -117,7 +117,7 @@ class ListingsViewControllerTestsStubbedViewModel: NSObject, ListingsViewModelTy var auctionID = "los-angeles-modern-auctions-march-2015" var syncInterval = SyncInterval var pageSize = 10 - var logSync: (NSDate) -> Void = { _ in} + var logSync: (Date) -> Void = { _ in} var numberOfSaleArtworks = 10 var showSpinner: Observable! = Observable.just(false) @@ -128,10 +128,10 @@ class ListingsViewControllerTestsStubbedViewModel: NSObject, ListingsViewModelTy return Observable.just(NSDate()) } - var scheduleOnBackground: (observable: Observable) -> Observable = { observable in observable } - var scheduleOnForeground: (observable: Observable<[SaleArtwork]>) -> Observable<[SaleArtwork]> = { observable in observable } + var scheduleOnBackground: (_ observable: Observable) -> Observable = { observable in observable } + var scheduleOnForeground: (_ observable: Observable<[SaleArtwork]>) -> Observable<[SaleArtwork]> = { observable in observable } - func saleArtworkViewModelAtIndexPath(indexPath: NSIndexPath) -> SaleArtworkViewModel { + func saleArtworkViewModelAtIndexPath(_ indexPath: NSIndexPath) -> SaleArtworkViewModel { let saleArtwork = testSaleArtwork() saleArtwork.lotNumber = lotNumber @@ -144,10 +144,10 @@ class ListingsViewControllerTestsStubbedViewModel: NSObject, ListingsViewModelTy return saleArtwork.viewModel } - func showDetailsForSaleArtworkAtIndexPath(indexPath: NSIndexPath) { } + func showDetailsForSaleArtworkAtIndexPath(_ indexPath: IndexPath) { } - func presentModalForSaleArtworkAtIndexPath(indexPath: NSIndexPath) { } - func imageAspectRatioForSaleArtworkAtIndexPath(indexPath: NSIndexPath) -> CGFloat? { return nil } + func presentModalForSaleArtworkAtIndexPath(_ indexPath: IndexPath) { } + func imageAspectRatioForSaleArtworkAtIndexPath(_ indexPath: IndexPath) -> CGFloat? { return nil } // Testing values var lotNumber: Int? diff --git a/KioskTests/ListingsViewModelTests.swift b/KioskTests/ListingsViewModelTests.swift index 7d340057..be1e3c6c 100644 --- a/KioskTests/ListingsViewModelTests.swift +++ b/KioskTests/ListingsViewModelTests.swift @@ -5,8 +5,8 @@ import Moya @testable import Kiosk -let testScheduleOnBackground: (observable: Observable) -> Observable = { observable in observable } -let testScheduleOnForeground: (observable: Observable<[SaleArtwork]>) -> Observable<[SaleArtwork]> = { observable in observable } +let testScheduleOnBackground: (_ observable: Observable) -> Observable = { observable in observable } +let testScheduleOnForeground: (_ observable: Observable<[SaleArtwork]>) -> Observable<[SaleArtwork]> = { observable in observable } class ListingsViewModelTests: QuickSpec { override func spec() { @@ -159,7 +159,7 @@ class ListingsViewModelTests: QuickSpec { } -func listingsDataForPage(page: Int, bidCount: Int, modelCount: Int?, reverseIDs: Bool = false) -> NSData { +func listingsData(forPage page: Int, bidCount: Int, modelCount: Int?, reverseIDs: Bool = false) -> NSData { let count = modelCount ?? (page == 1 ? 2 : 1) let models = Array(1...count).map { index -> NSDictionary in diff --git a/KioskTests/Models/ArtworkTests.swift b/KioskTests/Models/ArtworkTests.swift index 3f858320..ec3e3e07 100644 --- a/KioskTests/Models/ArtworkTests.swift +++ b/KioskTests/Models/ArtworkTests.swift @@ -14,7 +14,7 @@ class ArtworkTests: QuickSpec { let artistName = "Artist 1" let artistDict = ["id" : artistID, "name": artistName] - let data:[String: AnyObject] = ["id":id , "title" : title, "date":date, "blurb":blurb, "artist":artistDict] + let data: [String: AnyObject] = ["id":id as AnyObject , "title" : title as AnyObject, "date":date as AnyObject, "blurb":blurb as AnyObject, "artist":artistDict as AnyObject] var artwork: Artwork! diff --git a/KioskTests/Models/ImageTests.swift b/KioskTests/Models/ImageTests.swift index 18df9f16..ef9d391f 100644 --- a/KioskTests/Models/ImageTests.swift +++ b/KioskTests/Models/ImageTests.swift @@ -58,7 +58,7 @@ class ImageTests: QuickSpec { } } - func imageForVersion(version:String) -> Image { + func image(forVersion version:String) -> Image { return Image.fromJSON([ "id": "", "image_url":"http://image.com/:version.jpg", diff --git a/KioskTests/Models/SaleTests.swift b/KioskTests/Models/SaleTests.swift index 036a9af2..44402356 100644 --- a/KioskTests/Models/SaleTests.swift +++ b/KioskTests/Models/SaleTests.swift @@ -5,8 +5,8 @@ import Kiosk import ISO8601DateFormatter class SaleTests: QuickSpec { - func stringFromDate(date: NSDate) -> String { - return ISO8601DateFormatter().stringFromDate(date) + func stringFromDate(_ date: Date) -> String { + return ISO8601DateFormatter().string(from: date) } override func spec() { @@ -73,4 +73,4 @@ class SaleTests: QuickSpec { } } } -} \ No newline at end of file +} diff --git a/KioskTests/RegisterFlowViewTests.swift b/KioskTests/RegisterFlowViewTests.swift index f4ed98e3..c0f48784 100644 --- a/KioskTests/RegisterFlowViewTests.swift +++ b/KioskTests/RegisterFlowViewTests.swift @@ -7,7 +7,7 @@ import Nimble_Snapshots private let frame = CGRect(x: 0, y: 0, width: 180, height: 320) class RegisterFlowViewConfiguration: QuickConfiguration { - override class func configure(configuration: Configuration) { + override class func configure(_ configuration: Configuration) { sharedExamples("a register flow view") { (sharedExampleContext: SharedExampleContext) in var subject: RegisterFlowView! diff --git a/KioskTests/SaleArtworkDetailsViewControllerTests.swift b/KioskTests/SaleArtworkDetailsViewControllerTests.swift index bfa3fc7b..1d8806fe 100644 --- a/KioskTests/SaleArtworkDetailsViewControllerTests.swift +++ b/KioskTests/SaleArtworkDetailsViewControllerTests.swift @@ -6,7 +6,7 @@ import Nimble_Snapshots import SDWebImage class SaleArtworkDetailsViewControllerConfiguration: QuickConfiguration { - override class func configure(configuration: Configuration) { + override class func configure(_ configuration: Configuration) { sharedExamples("a sale artwork details view controller") { (sharedExampleContext: SharedExampleContext) in var subject: SaleArtworkDetailsViewController! @@ -22,7 +22,7 @@ class SaleArtworkDetailsViewControllerConfiguration: QuickConfiguration { } class SaleArtworkDetailsViewControllerTests: QuickSpec { - let imageCache = SDImageCache.sharedImageCache() + let imageCache = SDImageCache.shared() override func spec() { var subject: SaleArtworkDetailsViewController! diff --git a/KioskTests/TestHelpers.swift b/KioskTests/TestHelpers.swift index 3782533f..05014ff3 100644 --- a/KioskTests/TestHelpers.swift +++ b/KioskTests/TestHelpers.swift @@ -13,26 +13,26 @@ private enum DefaultsKeys: String { case TokenExpiry = "TokenExpiry" } -func clearDefaultsKeys(defaults: NSUserDefaults) { - defaults.removeObjectForKey(DefaultsKeys.TokenKey.rawValue) - defaults.removeObjectForKey(DefaultsKeys.TokenExpiry.rawValue) +func clearDefaultsKeys(_ defaults: UserDefaults) { + defaults.removeObject(forKey: DefaultsKeys.TokenKey.rawValue) + defaults.removeObject(forKey: DefaultsKeys.TokenExpiry.rawValue) } -func getDefaultsKeys(defaults: NSUserDefaults) -> (key: String?, expiry: NSDate?) { - let key = defaults.objectForKey(DefaultsKeys.TokenKey.rawValue) as! String? - let expiry = defaults.objectForKey(DefaultsKeys.TokenExpiry.rawValue) as! NSDate? +func getDefaultsKeys(_ defaults: UserDefaults) -> (key: String?, expiry: Date?) { + let key = defaults.object(forKey: DefaultsKeys.TokenKey.rawValue) as! String? + let expiry = defaults.object(forKey: DefaultsKeys.TokenExpiry.rawValue) as! Date? return (key: key, expiry: expiry) } -func setDefaultsKeys(defaults: NSUserDefaults, key: String?, expiry: NSDate?) { - defaults.setObject(key, forKey: DefaultsKeys.TokenKey.rawValue) - defaults.setObject(expiry, forKey: DefaultsKeys.TokenExpiry.rawValue) +func setDefaultsKeys(_ defaults: UserDefaults, key: String?, expiry: Date?) { + defaults.set(key, forKey: DefaultsKeys.TokenKey.rawValue) + defaults.set(expiry, forKey: DefaultsKeys.TokenExpiry.rawValue) } -func yearFromDate(date: NSDate) -> Int { - let calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)! - return calendar.components(.Year, fromDate: date).year +func yearFromDate(_ date: Date) -> Int { + let calendar = Calendar(identifier: Calendar.Identifier.gregorian) + return (calendar as NSCalendar).components(.year, from: date).year! } @objc class TestClass: NSObject { } @@ -40,7 +40,7 @@ func yearFromDate(date: NSDate) -> Int { // Necessary since UIImage(named:) doesn't work correctly in the test bundle extension UIImage { class func testImage(named name: String, ofType type: String) -> UIImage! { - let bundle = NSBundle(forClass: TestClass().dynamicType) + let bundle = Bundle(for: type(of: TestClass())) let path = bundle.pathForResource(name, ofType: type) return UIImage(contentsOfFile: path!) } @@ -90,14 +90,14 @@ class StubFulfillmentController: FulfillmentController { /// Nimble is currently having issues with nondeterministic async expectations. /// This will have to do for now 😢 /// See: https://github.com/Quick/Nimble/issues/177 -func kioskWaitUntil(action: (() -> Void) -> Void) { +func kioskWaitUntil(_ action: (() -> Void) -> Void) { waitUntil(timeout: 10, action: action) } // TODO: Move these into a separate pod? // This is handy so we can write expect(o) == 1 instead of expect(o.value) == 1 or whatever. -public func equalFirst(expectedValue: T?) -> MatcherFunc> { +public func equalFirst(_ expectedValue: T?) -> MatcherFunc> { return MatcherFunc { actualExpression, failureMessage in failureMessage.postfixMessage = "equal <\(expectedValue)>" @@ -108,7 +108,7 @@ public func equalFirst(expectedValue: T?) -> MatcherFunc(expectedValue: T?) -> MatcherFunc> { +public func equalFirst(_ expectedValue: T?) -> MatcherFunc> { return MatcherFunc { actualExpression, failureMessage in failureMessage.postfixMessage = "equal <\(expectedValue)>" @@ -119,7 +119,7 @@ public func equalFirst(expectedValue: T?) -> MatcherFunc(expectedValue: T?) -> MatcherFunc>> { +public func equalFirst(_ expectedValue: T?) -> MatcherFunc>> { return MatcherFunc { actualExpression, failureMessage in failureMessage.postfixMessage = "equal <\(expectedValue)>" @@ -134,7 +134,7 @@ public func equalFirst(expectedValue: T?) -> MatcherFunc(expectedValue: T?) -> MatcherFunc>> { +public func equalFirst(_ expectedValue: T?) -> MatcherFunc>> { return MatcherFunc { actualExpression, failureMessage in failureMessage.postfixMessage = "equal <\(expectedValue)>" @@ -172,7 +172,7 @@ enum TestError: String { case Default } -extension TestError: ErrorType { } +extension TestError: Error { } func emptyAction() -> CocoaAction { return CocoaAction { _ in Observable.empty() } @@ -182,7 +182,7 @@ func neverAction() -> CocoaAction { return CocoaAction { _ in Observable.never() } } -func errorAction(error: ErrorType = TestError.Default) -> CocoaAction { +func errorAction(_ error: ErrorType = TestError.Default) -> CocoaAction { return CocoaAction { _ in Observable.error(error) } } From b040c5d5e4d4c498630a39c9936509306111b799 Mon Sep 17 00:00:00 2001 From: Ash Furrow Date: Tue, 4 Oct 2016 16:43:52 -0400 Subject: [PATCH 03/46] [Swift 3] Fixed the easy bugs. --- Gemfile | 2 +- Gemfile.lock | 61 ++++----- Kiosk.xcodeproj/project.pbxproj | 116 +++++------------- Kiosk/App/CardHandler.swift | 10 +- Kiosk/App/Logger.swift | 24 ++-- Kiosk/App/Networking/ArtsyAPI.swift | 10 +- Kiosk/App/Networking/Networking.swift | 38 +++--- .../App/Views/Button Subclasses/Button.swift | 28 ++--- .../ListingsViewController.swift | 12 +- Kiosk/Bid Fulfillment/Models/BidDetails.swift | 12 +- .../RegistrationPasswordViewModel.swift | 10 +- Kiosk/Observable+Operators.swift | 26 ++-- Kiosk/Supporting Files/PodsBridgingHeader.h | 6 +- Podfile | 11 +- Podfile.lock | 28 ++--- 15 files changed, 177 insertions(+), 217 deletions(-) diff --git a/Gemfile b/Gemfile index 8445af8c..54770018 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,6 @@ source 'https://rubygems.org' -gem 'cocoapods' +gem 'cocoapods', '1.1.0.rc.2' gem 'cocoapods-keys' diff --git a/Gemfile.lock b/Gemfile.lock index a30a52a9..52f7b2f2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,7 +3,7 @@ GEM specs: RubyInline (3.12.4) ZenTest (~> 4.3) - ZenTest (4.11.0) + ZenTest (4.11.1) activesupport (4.2.7.1) i18n (~> 0.7) json (~> 1.7, >= 1.7.7) @@ -18,38 +18,42 @@ GEM cert (1.4.2) fastlane_core (>= 0.50.3, < 1.0.0) spaceship (>= 0.32.0, < 1.0.0) - claide (0.9.1) - cocoapods (0.39.0) - activesupport (>= 4.0.2) - claide (~> 0.9.1) - cocoapods-core (= 0.39.0) - cocoapods-downloader (~> 0.9.3) - cocoapods-plugins (~> 0.4.2) - cocoapods-search (~> 0.1.0) - cocoapods-stats (~> 0.6.2) - cocoapods-trunk (~> 0.6.4) - cocoapods-try (~> 0.5.1) + claide (1.0.0) + cocoapods (1.1.0.rc.2) + activesupport (>= 4.0.2, < 5) + claide (>= 1.0.0, < 2.0) + cocoapods-core (= 1.1.0.rc.2) + cocoapods-deintegrate (>= 1.0.1, < 2.0) + cocoapods-downloader (>= 1.1.1, < 2.0) + cocoapods-plugins (>= 1.0.0, < 2.0) + cocoapods-search (>= 1.0.0, < 2.0) + cocoapods-stats (>= 1.0.0, < 2.0) + cocoapods-trunk (>= 1.0.0, < 2.0) + cocoapods-try (>= 1.1.0, < 2.0) colored (~> 1.2) escape (~> 0.0.4) - molinillo (~> 0.4.0) + fourflusher (~> 1.0.1) + gh_inspector (~> 1.0) + molinillo (~> 0.5.1) nap (~> 1.0) - xcodeproj (~> 0.28.2) - cocoapods-core (0.39.0) - activesupport (>= 4.0.2) + xcodeproj (>= 1.3.1, < 2.0) + cocoapods-core (1.1.0.rc.2) + activesupport (>= 4.0.2, < 5) fuzzy_match (~> 2.0.4) nap (~> 1.0) - cocoapods-downloader (0.9.3) - cocoapods-keys (1.6.0) + cocoapods-deintegrate (1.0.1) + cocoapods-downloader (1.1.1) + cocoapods-keys (1.7.0) dotenv osx_keychain - cocoapods-plugins (0.4.2) + cocoapods-plugins (1.0.0) nap - cocoapods-search (0.1.0) - cocoapods-stats (0.6.2) - cocoapods-trunk (0.6.4) + cocoapods-search (1.0.0) + cocoapods-stats (1.0.0) + cocoapods-trunk (1.0.0) nap (>= 0.8, < 2.0) netrc (= 0.7.8) - cocoapods-try (0.5.1) + cocoapods-try (1.1.0) colored (1.2) commander (4.4.0) highline (~> 1.7.2) @@ -121,6 +125,7 @@ GEM plist (~> 3.1) rubyzip (~> 1.1.6) terminal-table (~> 1.4.5) + fourflusher (1.0.1) frameit (2.7.0) deliver (> 0.3) fastimage (~> 1.6.3) @@ -188,8 +193,8 @@ GEM mime-types-data (3.2016.0521) mini_magick (4.5.1) mini_portile2 (2.0.0) - minitest (5.9.0) - molinillo (0.4.2) + minitest (5.9.1) + molinillo (0.5.1) multi_json (1.12.1) multi_xml (0.5.5) multipart-post (2.0.0) @@ -275,9 +280,9 @@ GEM xcode-install (2.0.4) claide (>= 0.9.1, < 1.1.0) spaceship (>= 0.25.1, < 1.0.0) - xcodeproj (0.28.2) + xcodeproj (1.3.1) activesupport (>= 3) - claide (~> 0.9.1) + claide (>= 1.0.0, < 2.0) colored (~> 1.2) xcpretty (0.2.2) rouge (~> 1.8) @@ -288,7 +293,7 @@ PLATFORMS ruby DEPENDENCIES - cocoapods + cocoapods (= 1.1.0.rc.2) cocoapods-keys fastlane sbconstants diff --git a/Kiosk.xcodeproj/project.pbxproj b/Kiosk.xcodeproj/project.pbxproj index c70fdfc8..3e24cb54 100644 --- a/Kiosk.xcodeproj/project.pbxproj +++ b/Kiosk.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 3FCE6D8A07CA1FA29F4A37DC /* Pods_KioskTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AC6D95F6020DA76AECC315F /* Pods_KioskTests.framework */; }; 5E0A4E3E1B41C9C800CB2BEA /* UIView+LongPressDisplayMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E0A4E3D1B41C9C800CB2BEA /* UIView+LongPressDisplayMessage.swift */; }; 5E19E6501A6564B300F8CBDD /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E19E64E1A65630200F8CBDD /* Logger.swift */; }; 5E19E6521A656CCF00F8CBDD /* LoggerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E19E6511A656CCF00F8CBDD /* LoggerTests.swift */; }; @@ -222,7 +221,7 @@ 5EFA709C1A9674BD0082C916 /* BidderDetailsRetrieval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EFA709B1A9674BD0082C916 /* BidderDetailsRetrieval.swift */; }; 5EFFE24E1B977DD3006BF64C /* SaleArtworkViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EFFE24D1B977DD3006BF64C /* SaleArtworkViewModel.swift */; }; 5EFFE2501B978C3C006BF64C /* ListingsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EFFE24F1B978C3C006BF64C /* ListingsViewModelTests.swift */; }; - D12607F71BF578705637C9A7 /* Pods.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5DFB9BD12B5173B74D620D7 /* Pods.framework */; }; + D3F545A8B36E79DF2867D06B /* Pods_KioskTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A30D47B7655B0A0152E7FC55 /* Pods_KioskTests.framework */; }; E70556731BC40BFB0064ABF8 /* MyBidPosition.json in Resources */ = {isa = PBXBuildFile; fileRef = E70556721BC40BFB0064ABF8 /* MyBidPosition.json */; }; /* End PBXBuildFile section */ @@ -237,7 +236,6 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 3AC6D95F6020DA76AECC315F /* Pods_KioskTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_KioskTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 5E0A4E3D1B41C9C800CB2BEA /* UIView+LongPressDisplayMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UIView+LongPressDisplayMessage.swift"; path = "Kiosk/UIView+LongPressDisplayMessage.swift"; sourceTree = SOURCE_ROOT; }; 5E19E64E1A65630200F8CBDD /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; 5E19E6511A656CCF00F8CBDD /* LoggerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggerTests.swift; sourceTree = ""; }; @@ -400,6 +398,7 @@ 5E8611001A5C43EF00456E41 /* TextFieldTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldTests.swift; sourceTree = ""; }; 5E8611011A5C43EF00456E41 /* XAppTokenSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XAppTokenSpec.swift; sourceTree = ""; }; 5E87BBA71C03C39F00D46BB5 /* Observable+JSONAble.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Observable+JSONAble.swift"; sourceTree = ""; }; + 5E8B22F41DA445CE008D1EB4 /* Gemfile */ = {isa = PBXFileReference; lastKnownFileType = text; path = Gemfile; sourceTree = ""; }; 5E9F8AD31A8951C100CBEACE /* SaleArtworkDetailsViewControllerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SaleArtworkDetailsViewControllerTests.swift; sourceTree = ""; }; 5EA1F7281BFBC84E00F90655 /* HelperFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HelperFunctions.swift; sourceTree = ""; }; 5EA1F72C1BFE672200F90655 /* UIKit+Rx.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIKit+Rx.swift"; sourceTree = ""; }; @@ -438,12 +437,11 @@ 5EFA709B1A9674BD0082C916 /* BidderDetailsRetrieval.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BidderDetailsRetrieval.swift; sourceTree = ""; }; 5EFFE24D1B977DD3006BF64C /* SaleArtworkViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SaleArtworkViewModel.swift; sourceTree = ""; }; 5EFFE24F1B978C3C006BF64C /* ListingsViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListingsViewModelTests.swift; sourceTree = ""; }; + 871AC5A32DB43C035EC4933D /* Pods-KioskTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KioskTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-KioskTests/Pods-KioskTests.release.xcconfig"; sourceTree = ""; }; + A30D47B7655B0A0152E7FC55 /* Pods_KioskTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_KioskTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B5DFB9BD12B5173B74D620D7 /* Pods.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - BDA79D9FDF198B37A06B262F /* Pods-KioskTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KioskTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-KioskTests/Pods-KioskTests.release.xcconfig"; sourceTree = ""; }; - CB3EBEE2789DABF779E6EB67 /* Pods-KioskTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KioskTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-KioskTests/Pods-KioskTests.debug.xcconfig"; sourceTree = ""; }; E70556721BC40BFB0064ABF8 /* MyBidPosition.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = MyBidPosition.json; sourceTree = ""; }; - EF3D9FFB0326E925ABE58D3F /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; }; - EF4EAF6B170C35E859BFCC0A /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = ""; }; + F36F2D0360FAFC13067DE7EE /* Pods-KioskTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KioskTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-KioskTests/Pods-KioskTests.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -451,7 +449,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - D12607F71BF578705637C9A7 /* Pods.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -459,13 +456,22 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 3FCE6D8A07CA1FA29F4A37DC /* Pods_KioskTests.framework in Frameworks */, + D3F545A8B36E79DF2867D06B /* Pods_KioskTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 5804E77C62062C5C2F916D18 /* Pods */ = { + isa = PBXGroup; + children = ( + F36F2D0360FAFC13067DE7EE /* Pods-KioskTests.debug.xcconfig */, + 871AC5A32DB43C035EC4933D /* Pods-KioskTests.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; 5E860FC21A5C429C00456E41 /* Admin */ = { isa = PBXGroup; children = ( @@ -762,11 +768,12 @@ isa = PBXGroup; children = ( 5E3102ED1BF14E2500CA1823 /* CHANGELOG.yml */, + 5E8B22F41DA445CE008D1EB4 /* Gemfile */, 5EB0B6EF1A5C3E6800B7CBF2 /* Kiosk */, 5EB0B7051A5C3E6800B7CBF2 /* KioskTests */, 5EB0B6EE1A5C3E6800B7CBF2 /* Products */, - D6412E48092F2A37CE44DBD3 /* Pods */, EDE935AE93E27C7CD4C012CE /* Frameworks */, + 5804E77C62062C5C2F916D18 /* Pods */, ); sourceTree = ""; }; @@ -875,22 +882,11 @@ name = Registration; sourceTree = ""; }; - D6412E48092F2A37CE44DBD3 /* Pods */ = { - isa = PBXGroup; - children = ( - EF4EAF6B170C35E859BFCC0A /* Pods.debug.xcconfig */, - EF3D9FFB0326E925ABE58D3F /* Pods.release.xcconfig */, - CB3EBEE2789DABF779E6EB67 /* Pods-KioskTests.debug.xcconfig */, - BDA79D9FDF198B37A06B262F /* Pods-KioskTests.release.xcconfig */, - ); - name = Pods; - sourceTree = ""; - }; EDE935AE93E27C7CD4C012CE /* Frameworks */ = { isa = PBXGroup; children = ( B5DFB9BD12B5173B74D620D7 /* Pods.framework */, - 3AC6D95F6020DA76AECC315F /* Pods_KioskTests.framework */, + A30D47B7655B0A0152E7FC55 /* Pods_KioskTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -902,12 +898,9 @@ isa = PBXNativeTarget; buildConfigurationList = 5EB0B70C1A5C3E6800B7CBF2 /* Build configuration list for PBXNativeTarget "Kiosk" */; buildPhases = ( - 55688295E50AAE994777FCE0 /* Check Pods Manifest.lock */, 5EB0B6E91A5C3E6800B7CBF2 /* Sources */, 5EB0B6EA1A5C3E6800B7CBF2 /* Frameworks */, 5EB0B6EB1A5C3E6800B7CBF2 /* Resources */, - 0ED47F96B607F7D4857C5E68 /* Embed Pods Frameworks */, - F333900A5C093117E35A7BD5 /* Copy Pods Resources */, ); buildRules = ( ); @@ -922,12 +915,12 @@ isa = PBXNativeTarget; buildConfigurationList = 5EB0B70F1A5C3E6800B7CBF2 /* Build configuration list for PBXNativeTarget "KioskTests" */; buildPhases = ( - 901E7D22172832AE508507D2 /* Check Pods Manifest.lock */, + 3713EC04F59C7386009C49D6 /* [CP] Check Pods Manifest.lock */, 5EB0B6FE1A5C3E6800B7CBF2 /* Sources */, 5EB0B6FF1A5C3E6800B7CBF2 /* Frameworks */, 5EB0B7001A5C3E6800B7CBF2 /* Resources */, - 908DC06FFFC16E13DB5E5739 /* Embed Pods Frameworks */, - 0CF23BEE7B57C218177741BD /* Copy Pods Resources */, + 8F886D2A54F4ECF13F03A562 /* [CP] Embed Pods Frameworks */, + EBD00B3B35AA6E619C9CDB8B /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -1053,74 +1046,29 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 0CF23BEE7B57C218177741BD /* Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Copy Pods Resources"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-KioskTests/Pods-KioskTests-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; - 0ED47F96B607F7D4857C5E68 /* Embed Pods Frameworks */ = { + 3713EC04F59C7386009C49D6 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Embed Pods Frameworks"; + name = "[CP] Check Pods Manifest.lock"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh\"\n"; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; - 55688295E50AAE994777FCE0 /* Check Pods Manifest.lock */ = { + 8F886D2A54F4ECF13F03A562 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Check Pods Manifest.lock"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - 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"; - showEnvVarsInLog = 0; - }; - 901E7D22172832AE508507D2 /* Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Check Pods Manifest.lock"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - 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"; - showEnvVarsInLog = 0; - }; - 908DC06FFFC16E13DB5E5739 /* Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Embed Pods Frameworks"; + name = "[CP] Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -1128,19 +1076,19 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-KioskTests/Pods-KioskTests-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - F333900A5C093117E35A7BD5 /* Copy Pods Resources */ = { + EBD00B3B35AA6E619C9CDB8B /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Copy Pods Resources"; + name = "[CP] Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n"; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-KioskTests/Pods-KioskTests-resources.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -1414,7 +1362,6 @@ }; 5EB0B70D1A5C3E6800B7CBF2 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = EF4EAF6B170C35E859BFCC0A /* Pods.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; @@ -1441,7 +1388,6 @@ }; 5EB0B70E1A5C3E6800B7CBF2 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = EF3D9FFB0326E925ABE58D3F /* Pods.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; @@ -1469,7 +1415,7 @@ }; 5EB0B7101A5C3E6800B7CBF2 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = CB3EBEE2789DABF779E6EB67 /* Pods-KioskTests.debug.xcconfig */; + baseConfigurationReference = F36F2D0360FAFC13067DE7EE /* Pods-KioskTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ENABLE_MODULES = YES; @@ -1495,7 +1441,7 @@ }; 5EB0B7111A5C3E6800B7CBF2 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = BDA79D9FDF198B37A06B262F /* Pods-KioskTests.release.xcconfig */; + baseConfigurationReference = 871AC5A32DB43C035EC4933D /* Pods-KioskTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ENABLE_MODULES = YES; diff --git a/Kiosk/App/CardHandler.swift b/Kiosk/App/CardHandler.swift index b70be259..778364bf 100644 --- a/Kiosk/App/CardHandler.swift +++ b/Kiosk/App/CardHandler.swift @@ -15,7 +15,7 @@ class CardHandler: NSObject, CFTReaderDelegate { let APIToken: String var reader: CFTReader! - lazy var sessionManager = CFTSessionManager.sharedInstance() + lazy var sessionManager = CFTSessionManager.sharedInstance()! init(apiKey: String, accountToken: String){ APIKey = apiKey @@ -40,12 +40,12 @@ class CardHandler: NSObject, CFTReaderDelegate { reader = nil } - func readerCardResponse(_ card: CFTCard?, withError error: NSError?) { + func readerCardResponse(_ card: CFTCard?, withError error: Error?) { if let card = card { self.card = card; _cardStatus.onNext("Got Card") - card.tokenizeCardWithSuccess({ [weak self] in + card.tokenizeCard(success: { [weak self] in self?._cardStatus.onCompleted() logger.log("Card was tokenized") @@ -63,7 +63,7 @@ class CardHandler: NSObject, CFTReaderDelegate { } } - func transactionResult(_ charge: CFTCharge!, withError error: NSError!) { + func transactionResult(_ charge: CFTCharge!, withError error: Error!) { logger.log("Unexcepted call to transactionResult callback: \(charge)\n\(error)") } @@ -92,7 +92,7 @@ class CardHandler: NSObject, CFTReaderDelegate { reader.beginSwipe() } - func readerIsConnected(_ isConnected: Bool, withError error: NSError!) { + func readerIsConnected(_ isConnected: Bool, withError error: Error!) { if isConnected { _cardStatus.onNext("Reader is connected") reader.beginSwipe() diff --git a/Kiosk/App/Logger.swift b/Kiosk/App/Logger.swift index d6a1cd7f..b54a8a8e 100644 --- a/Kiosk/App/Logger.swift +++ b/Kiosk/App/Logger.swift @@ -10,19 +10,15 @@ class Logger { return formatter }() lazy fileprivate var fileHandle: FileHandle? = { - if let path = self.destination.path { - FileManager.default.createFile(atPath: path, contents: nil, attributes: nil) - - do { - let fileHandle = try FileHandle(forWritingTo: self.destination) - print("Successfully logging to: \(path)") - return fileHandle - } catch let error as NSError { - print("Serious error in logging: could not open path to log file. \(error).") - } - - } else { - print("Serious error in logging: specified destination (\(self.destination)) does not appear to have a path component.") + let path = self.destination.path + FileManager.default.createFile(atPath: path, contents: nil, attributes: nil) + + do { + let fileHandle = try FileHandle(forWritingTo: self.destination) + print("Successfully logging to: \(path)") + return fileHandle + } catch let error as NSError { + print("Serious error in logging: could not open path to log file. \(error).") } return nil @@ -48,7 +44,7 @@ private extension Logger { func stringRepresentation(_ message: String, function: String, file: String, line: Int) -> String { let dateString = dateFormatter.string(from: Date()) - let file = URL(fileURLWithPath: file).lastPathComponent ?? "(Unknown File)" + let file = URL(fileURLWithPath: file).lastPathComponent return "\(dateString) [\(file):\(line)] \(function): \(message)\n" } diff --git a/Kiosk/App/Networking/ArtsyAPI.swift b/Kiosk/App/Networking/ArtsyAPI.swift index 35b81b8c..cceef650 100644 --- a/Kiosk/App/Networking/ArtsyAPI.swift +++ b/Kiosk/App/Networking/ArtsyAPI.swift @@ -49,6 +49,10 @@ enum ArtsyAuthenticatedAPI { } extension ArtsyAPI : TargetType, ArtsyAPIType { + public var parameters: [String : Any]? { + <#code#> + } + var path: String { switch self { @@ -118,7 +122,7 @@ extension ArtsyAPI : TargetType, ArtsyAPIType { "client_secret": APIKeys.sharedKeys.secret as AnyObject? ?? "" as AnyObject, "email": email as AnyObject, "password": password as AnyObject, - "grant_type": "credentials" + "grant_type": "credentials" as AnyObject ] case .xApp: @@ -243,6 +247,10 @@ extension ArtsyAPI : TargetType, ArtsyAPIType { } extension ArtsyAuthenticatedAPI: TargetType, ArtsyAPIType { + public var parameters: [String : Any]? { + <#code#> + } + var path: String { switch self { diff --git a/Kiosk/App/Networking/Networking.swift b/Kiosk/App/Networking/Networking.swift index c98918b3..c29c65a4 100644 --- a/Kiosk/App/Networking/Networking.swift +++ b/Kiosk/App/Networking/Networking.swift @@ -8,9 +8,9 @@ class OnlineProvider: RxMoyaProvider where Target: TargetType { fileprivate let online: Observable - init(endpointClosure: MoyaProvider.EndpointClosure = MoyaProvider.DefaultEndpointMapping, - requestClosure: MoyaProvider.RequestClosure = MoyaProvider.DefaultRequestMapping, - stubClosure: MoyaProvider.StubClosure = MoyaProvider.NeverStub, + init(endpointClosure: @escaping MoyaProvider.EndpointClosure = MoyaProvider.DefaultEndpointMapping, + requestClosure: @escaping MoyaProvider.RequestClosure = MoyaProvider.DefaultRequestMapping, + stubClosure: @escaping MoyaProvider.StubClosure = MoyaProvider.NeverStub, manager: Manager = Alamofire.Manager.sharedInstance, plugins: [PluginType] = [], online: Observable = connectedToInternetOrStubbing()) { @@ -22,7 +22,7 @@ class OnlineProvider: RxMoyaProvider where Target: TargetType { override func request(_ token: Target) -> Observable { let actualRequest = super.request(token) return online - .ignore(false) // Wait until we're online + .ignore(value: false) // Wait until we're online .take(1) // Take 1 to make sure we only invoke the API once. .flatMap { _ in // Turn the online state into a network request return actualRequest @@ -49,7 +49,7 @@ struct AuthorizedNetworking: NetworkingType { private extension Networking { /// Request to fetch and store new XApp token if the current token is missing or expired. - func XAppTokenRequest(_ defaults: NSUserDefaults) -> Observable { + func XAppTokenRequest(_ defaults: UserDefaults) -> Observable { var appToken = XAppToken(defaults: defaults) @@ -58,7 +58,7 @@ private extension Networking { return Observable.just(appToken.token) } - let newTokenRequest = self.provider.request(ArtsyAPI.XApp) + let newTokenRequest = self.provider.request(ArtsyAPI.xApp) .filterSuccessfulStatusCodes() .mapJSON() .map { element -> (token: String?, expiry: String?) in @@ -66,10 +66,10 @@ private extension Networking { return (token: dictionary["xapp_token"] as? String, expiry: dictionary["expires_in"] as? String) } - .doOn { event in - guard case Event.Next(let element) = event else { return } + .do { event in + guard case Event.next(let element) = event else { return } - let formatter = ISO8601DateFormatter() + let formatter = ISO8601DateFormatter.ISO8601DateFormatter() // These two lines set the defaults values injected into appToken appToken.token = element.0 appToken.expiry = formatter.dateFromString(element.1) @@ -86,7 +86,7 @@ private extension Networking { // "Public" interfaces extension Networking { /// Request to fetch a given target. Ensures that valid XApp tokens exist before making request - func request(_ token: ArtsyAPI, defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()) -> Observable { + func request(_ token: ArtsyAPI, defaults: UserDefaults = UserDefaults.standard) -> Observable { let actualRequest = self.provider.request(token) return self.XAppTokenRequest(defaults).flatMap { _ in actualRequest } @@ -94,7 +94,7 @@ extension Networking { } extension AuthorizedNetworking { - func request(_ token: ArtsyAuthenticatedAPI, defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()) -> Observable { + func request(_ token: ArtsyAuthenticatedAPI, defaults: UserDefaults = UserDefaults.standard) -> Observable { return self.provider.request(token) } } @@ -120,7 +120,7 @@ extension NetworkingType { static func endpointsClosure(_ xAccessToken: String? = nil) -> (T) -> Endpoint where T: TargetType, T: ArtsyAPIType { return { target in - var endpoint: Endpoint = Endpoint(URL: url(target), sampleResponseClosure: {.NetworkResponse(200, target.sampleData)}, method: target.method, parameters: target.parameters) + var endpoint: Endpoint = Endpoint(URL: url(target), sampleResponseClosure: {.networkResponse(200, target.sampleData)}, method: target.method, parameters: target.parameters) // If we were given an xAccessToken, add it if let xAccessToken = xAccessToken { @@ -137,7 +137,7 @@ extension NetworkingType { } static func APIKeysBasedStubBehaviour(_: T) -> Moya.StubBehavior { - return APIKeys.sharedKeys.stubResponses ? .Immediate : .Never + return APIKeys.sharedKeys.stubResponses ? .immediate : .never } static var plugins: [PluginType] { @@ -146,7 +146,7 @@ extension NetworkingType { guard let target = target as? ArtsyAPI else { return false } switch target { - case .Ping: return true + case .ping: return true default: return false } }) @@ -157,8 +157,8 @@ extension NetworkingType { guard let target = target as? ArtsyAuthenticatedAPI else { return false } switch target { - case .MyBidPosition: return true - case .FindMyBidderRegistration: return true + case .myBidPosition: return true + case .findMyBidderRegistration: return true default: return false } }) @@ -168,9 +168,9 @@ extension NetworkingType { // (Endpoint, NSURLRequest -> Void) -> Void static func endpointResolver() -> MoyaProvider.RequestClosure where T: TargetType { return { (endpoint, closure) in - let request: NSMutableURLRequest = endpoint.urlRequest.mutableCopy() as! NSMutableURLRequest - request.HTTPShouldHandleCookies = false - closure(request) + var request = endpoint.urlRequest! + request.httpShouldHandleCookies = false + closure(.success(request)) } } } diff --git a/Kiosk/App/Views/Button Subclasses/Button.swift b/Kiosk/App/Views/Button Subclasses/Button.swift index a580e12e..b048c3a5 100644 --- a/Kiosk/App/Views/Button Subclasses/Button.swift +++ b/Kiosk/App/Views/Button Subclasses/Button.swift @@ -6,9 +6,9 @@ class Button: ARFlatButton { override func setup() { super.setup() - setTitleShadowColor(.clear(), for: UIControlState()) - setTitleShadowColor(.clear(), for: .highlighted) - setTitleShadowColor(.clear(), for: .disabled) + setTitleShadowColor(UIColor.clear, for: UIControlState()) + setTitleShadowColor(UIColor.clear, for: .highlighted) + setTitleShadowColor(UIColor.clear, for: .disabled) shouldDimWhenDisabled = false; } } @@ -22,16 +22,16 @@ class ActionButton: Button { override func setup() { super.setup() - setBorderColor(.black(), for: UIControlState(), animated: false) + setBorderColor(.black, for: UIControlState(), animated: false) setBorderColor(.artsyPurple(), for: .highlighted, animated: false) setBorderColor(.artsyMediumGrey(), for: .disabled, animated: false) - setBackgroundColor(.black(), for: UIControlState(), animated: false) + setBackgroundColor(.black, for: UIControlState(), animated: false) setBackgroundColor(.artsyPurple(), for: .highlighted, animated: false) - setBackgroundColor(.white(), for: .disabled, animated: false) + setBackgroundColor(.white, for: .disabled, animated: false) - setTitleColor(.white(), for:UIControlState()) - setTitleColor(.white(), for:.highlighted) + setTitleColor(.white, for:UIControlState()) + setTitleColor(.white, for:.highlighted) setTitleColor(.artsyHeavyGrey(), for:.disabled) } } @@ -49,12 +49,12 @@ class SecondaryActionButton: Button { setBorderColor(.artsyPurple(), for: .highlighted, animated: false) setBorderColor(.artsyLightGrey(), for: .disabled, animated: false) - setBackgroundColor(.white(), for: UIControlState(), animated: false) + setBackgroundColor(.white, for: UIControlState(), animated: false) setBackgroundColor(.artsyPurple(), for: .highlighted, animated: false) - setBackgroundColor(.white(), for: .disabled, animated: false) + setBackgroundColor(.white, for: .disabled, animated: false) - setTitleColor(.black(), for:UIControlState()) - setTitleColor(.white(), for:.highlighted) + setTitleColor(.black, for:UIControlState()) + setTitleColor(.white, for:.highlighted) setTitleColor(.artsyHeavyGrey(), for:.disabled) } } @@ -66,8 +66,8 @@ class KeypadButton: Button { super.setup() shouldAnimateStateChange = false; layer.borderWidth = 0 - setBackgroundColor(.black(), for: .highlighted, animated: false) - setBackgroundColor(.white(), for: UIControlState(), animated: false) + setBackgroundColor(.black, for: .highlighted, animated: false) + setBackgroundColor(.white, for: UIControlState(), animated: false) } } diff --git a/Kiosk/Auction Listings/ListingsViewController.swift b/Kiosk/Auction Listings/ListingsViewController.swift index 0cabddea..eb0e43f4 100644 --- a/Kiosk/Auction Listings/ListingsViewController.swift +++ b/Kiosk/Auction Listings/ListingsViewController.swift @@ -94,7 +94,7 @@ class ListingsViewController: UIViewController { // Reload collection view when there is new content. viewModel .updatedContents - .mapReplace(collectionView) + .mapReplace(with: collectionView) .doOnNext { collectionView in collectionView.reloadData() } @@ -104,7 +104,7 @@ class ListingsViewController: UIViewController { guard let _ = self?.view.window else { return } // Need to dispatchAsyncMainScheduler, since the changes in the CV's model aren't imediate, so we may scroll to a cell that doesn't exist yet. - collectionView.scrollToItemAtIndexPath(NSIndexPath(forItem: 0, inSection: 0), atScrollPosition: .Top, animated: false) + collectionView.scrollToItem(at: IndexPath(item: 0, section: 0), at: .top, animated: false) } .addDisposableTo(rx_disposeBag) @@ -116,7 +116,7 @@ class ListingsViewController: UIViewController { case true: return ListingsViewController.masonryLayout() default: - return ListingsViewController.tableLayout(CGRectGetWidth(self?.switchView.frame ?? CGRectZero)) + return ListingsViewController.tableLayout(width: (self?.switchView.frame ?? CGRectZero).width) } } .subscribeNext { [weak self] layout in @@ -137,7 +137,7 @@ class ListingsViewController: UIViewController { } override func viewWillAppear(_ animated: Bool) { - let switchHeightPredicate = "\(switchView.intrinsicContentSize().height)" + let switchHeightPredicate = "\(switchView.intrinsicContentSize.height)" switchView.constrainHeight(switchHeightPredicate) switchView.alignTop("\(64+VerticalMargins)", leading: "\(HorizontalMargins)", bottom: nil, trailing: "-\(HorizontalMargins)", to: view) @@ -162,7 +162,7 @@ extension ListingsViewController: UICollectionViewDataSource, UICollectionViewDe } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - let cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellIdentifier.value, forIndexPath: indexPath) + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier.value, for: indexPath) if let listingsCell = cell as? ListingsCollectionViewCell { @@ -206,7 +206,7 @@ private extension ListingsViewController { } func presentModalForSaleArtwork(_ saleArtwork: SaleArtwork) { - bid(viewModel.auctionID, saleArtwork: saleArtwork, allowAnimations: self.allowAnimations, provider: provider) + bid(auctionID: viewModel.auctionID, saleArtwork: saleArtwork, allowAnimations: self.allowAnimations, provider: provider) } // MARK: Class methods diff --git a/Kiosk/Bid Fulfillment/Models/BidDetails.swift b/Kiosk/Bid Fulfillment/Models/BidDetails.swift index c1215626..cf5f743c 100644 --- a/Kiosk/Bid Fulfillment/Models/BidDetails.swift +++ b/Kiosk/Bid Fulfillment/Models/BidDetails.swift @@ -3,7 +3,7 @@ import RxSwift import Moya @objc class BidDetails: NSObject { - typealias DownloadImageClosure = (url: NSURL, imageView: UIImageView) -> () + typealias DownloadImageClosure = (_ url: URL, _ imageView: UIImageView) -> () let auctionID: String @@ -16,7 +16,7 @@ import Moya var bidderID = Variable(nil) var setImage: DownloadImageClosure = { (url, imageView) -> () in - imageView.sd_setImageWithURL(url) + imageView.sd_setImage(with: url) } init(saleArtwork: SaleArtwork?, paddleNumber: String?, bidderPIN: String?, bidAmountCents: Int?, auctionID: String) { @@ -24,7 +24,7 @@ import Moya self.saleArtwork = saleArtwork self.paddleNumber.value = paddleNumber self.bidderPIN.value = bidderPIN - self.bidAmountCents.value = bidAmountCents + self.bidAmountCents.value = bidAmountCents as NSNumber? } /// Creates a new authenticated networking provider based on either: @@ -47,7 +47,7 @@ import Moya return .just(AuthorizedNetworking(provider: provider)) } else { - let endpoint: ArtsyAPI = ArtsyAPI.XAuth(email: newUser.email.value ?? "", password: newUser.password.value ?? "") + let endpoint: ArtsyAPI = ArtsyAPI.xAuth(email: newUser.email.value ?? "", password: newUser.password.value ?? "") return provider.request(endpoint) .filterSuccessfulStatusCodes() @@ -59,7 +59,7 @@ import Moya return .just(Networking.newAuthorizedNetworking(accessToken)) } - .logServerError("Getting Access Token failed.") + .logServerError(message: "Getting Access Token failed.") } } -} \ No newline at end of file +} diff --git a/Kiosk/Bid Fulfillment/RegistrationPasswordViewModel.swift b/Kiosk/Bid Fulfillment/RegistrationPasswordViewModel.swift index 10d12d9c..fc63eac4 100644 --- a/Kiosk/Bid Fulfillment/RegistrationPasswordViewModel.swift +++ b/Kiosk/Bid Fulfillment/RegistrationPasswordViewModel.swift @@ -27,7 +27,7 @@ class RegistrationPasswordViewModel: RegistrationPasswordViewModelType { self.email = email let checkEmail = provider - .request(ArtsyAPI.FindExistingEmailRegistration(email: email)) + .request(ArtsyAPI.findExistingEmailRegistration(email: email)) .map(responseIsOK) .shareReplay(1) @@ -40,19 +40,19 @@ class RegistrationPasswordViewModel: RegistrationPasswordViewModelType { // Action takes nothing, is enabled if the password is valid, and does the following: // Check if the email exists, it tries to log in. // If it doesn't exist, then it does nothing. - let action = CocoaAction(enabledIf: password.asObservable().map(isStringLengthAtLeast(6))) { _ in + let action = CocoaAction(enabledIf: password.asObservable().map(isStringLengthAtLeast(length: 6))) { _ in return self.emailExists .flatMap { exists -> Observable in if exists { - let endpoint: ArtsyAPI = ArtsyAPI.XAuth(email: email, password: password.value ?? "") + let endpoint: ArtsyAPI = ArtsyAPI.xAuth(email: email, password: password.value ) return provider .request(endpoint) .filterSuccessfulStatusCodes() .map(void) } else { // Return a non-empty observable, so that the action sends something on its elements observable. - return .just() + return .just(Void()) } } .doOnCompleted { @@ -63,7 +63,7 @@ class RegistrationPasswordViewModel: RegistrationPasswordViewModelType { self.action = action execute - .subscribeNext { _ in + .subscribe { _ in action.execute(Void()) } .addDisposableTo(disposeBag) diff --git a/Kiosk/Observable+Operators.swift b/Kiosk/Observable+Operators.swift index 240a1da4..09eb9c33 100644 --- a/Kiosk/Observable+Operators.swift +++ b/Kiosk/Observable+Operators.swift @@ -20,7 +20,7 @@ extension Observable { // // Still not sure if this is a good idea. - func flatMapTo(_ selector: (Element) -> () -> Observable) -> Observable { + func flatMapTo(_ selector: @escaping (Element) -> () -> Observable) -> Observable { return self.map { (s) -> Observable in return selector(s)() }.switchLatest() @@ -63,30 +63,30 @@ extension Observable where Element: OptionalType { // TODO: Added in new RxSwift? extension Observable { - func doOnNext(_ closure: (Element) -> Void) -> Observable { + func doOnNext(_ closure: @escaping (Element) -> Void) -> Observable { return doOn { (event: Event) in switch event { - case .Next(let value): + case .next(let value): closure(value) default: break } } } - func doOnCompleted(_ closure: () -> Void) -> Observable { + func doOnCompleted(_ closure: @escaping () -> Void) -> Observable { return doOn { (event: Event) in switch event { - case .Completed: + case .completed: closure() default: break } } } - func doOnError(_ closure: (ErrorType) -> Void) -> Observable { + func doOnError(_ closure: @escaping (Error) -> Void) -> Observable { return doOn { (event: Event) in switch event { - case .Error(let error): + case .error(let error): closure(error) default: break } @@ -94,7 +94,7 @@ extension Observable { } } -private let backgroundScheduler = SerialDispatchQueueScheduler(globalConcurrentQueueQOS: .Default) +private let backgroundScheduler = SerialDispatchQueueScheduler(globalConcurrentQueueQOS: .default) extension Observable { func mapReplace(with value: T) -> Observable { @@ -109,7 +109,7 @@ extension Observable { } // Maps true to false and vice versa -extension Observable where Element: BooleanType { +extension Observable where Element: Bool { func not() -> Observable { return self.map { input in return !input.boolValue @@ -138,13 +138,13 @@ extension Collection where Iterator.Element: ObservableType, Iterator.Element.E: extension ObservableType { - func then(_ closure: () -> Observable?) -> Observable { + func then(_ closure: @escaping () -> Observable?) -> Observable { return then(closure() ?? .empty()) } - func then(@autoclosure(escaping) _ closure: () -> Observable) -> Observable { + func then( _ closure: @autoclosure @escaping () -> Observable) -> Observable { let next = Observable.deferred { - return closure() ?? .empty() + return closure() } return self @@ -160,7 +160,7 @@ extension Observable { } func sendDispatchCompleted(to observer: AnyObserver) { - dispatch_async(dispatch_get_main_queue()) { + DispatchQueue.main.async { observer.onCompleted() } } diff --git a/Kiosk/Supporting Files/PodsBridgingHeader.h b/Kiosk/Supporting Files/PodsBridgingHeader.h index 762735f6..a0f6421e 100644 --- a/Kiosk/Supporting Files/PodsBridgingHeader.h +++ b/Kiosk/Supporting Files/PodsBridgingHeader.h @@ -13,9 +13,9 @@ #import #import -#import -#import -#import +//#import +//#import +//#import #import #import diff --git a/Podfile b/Podfile index 07304b6c..cb58580e 100644 --- a/Podfile +++ b/Podfile @@ -54,7 +54,7 @@ pod 'DZNWebViewController', :git => 'https://github.com/orta/DZNWebViewControlle pod 'Reachability', :git => 'https://github.com/ashfurrow/Reachability.git', :branch => 'frameworks' pod 'UIView+BooleanAnimations' -pod 'ARTiledImageView', :git => 'https://github.com/ashfurrow/ARTiledImageView.git' +pod 'ARTiledImageView' pod 'XNGMarkdownParser' # Swift pods @@ -76,3 +76,12 @@ target 'KioskTests' do pod 'RxBlocking', '3.0.0-beta.2' end + +post_install do |installer| + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['SWIFT_VERSION'] = '3.0' + config.build_settings['MACOSX_DEPLOYMENT_TARGET'] = '10.10' + end + end +end diff --git a/Podfile.lock b/Podfile.lock index 6383b916..6d19606e 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -11,8 +11,8 @@ PODS: - ARAnalytics/CoreIOS - Mixpanel - ARCollectionViewMasonryLayout (2.0.0) - - ARTiledImageView (1.2.0): - - SDWebImage/Core + - ARTiledImageView (1.1.1): + - SDWebImage - Artsy+UIColors (1.0.0): - EDColor (~> 0.4) - Artsy+UIFonts (1.1.0) @@ -62,7 +62,7 @@ PODS: - NJKWebViewProgress/ProgressView (0.2.3) - NSObject+Rx (2.0.0): - RxSwift - - ORStackView (2.0.0): + - ORStackView (3.0.0): - FLKAutoLayout (~> 0.1) - Quick (0.10.0) - Reachability (3.1.1) @@ -90,7 +90,7 @@ DEPENDENCIES: - ARAnalytics/HockeyApp - ARAnalytics/Mixpanel - ARCollectionViewMasonryLayout (~> 2.0.0) - - ARTiledImageView (from `https://github.com/ashfurrow/ARTiledImageView.git`) + - ARTiledImageView - Artsy+UIColors - Artsy+UIFonts (~> 1.1.0) - Artsy+UILabels @@ -98,8 +98,7 @@ DEPENDENCIES: - CardFlight - DZNWebViewController (from `https://github.com/orta/DZNWebViewController.git`) - ECPhoneNumberFormatter - - FBSnapshotTestCase (from `https://github.com/facebook/ios-snapshot-test-case`, - commit `1639f694a2100cdeadf2f8fa14225cdd3759e75b`) + - FBSnapshotTestCase (from `https://github.com/facebook/ios-snapshot-test-case`, commit `1639f694a2100cdeadf2f8fa14225cdd3759e75b`) - FLKAutoLayout - Forgeries - ISO8601DateFormatter (= 0.7) @@ -123,8 +122,6 @@ DEPENDENCIES: - XNGMarkdownParser EXTERNAL SOURCES: - ARTiledImageView: - :git: https://github.com/ashfurrow/ARTiledImageView.git DZNWebViewController: :git: https://github.com/orta/DZNWebViewController.git FBSnapshotTestCase: @@ -139,9 +136,6 @@ EXTERNAL SOURCES: :git: https://github.com/ashfurrow/UIImageViewAligned.git CHECKOUT OPTIONS: - ARTiledImageView: - :commit: 2fe35ede0cb1d8cf3dc5aa4506c476c0367564b7 - :git: https://github.com/ashfurrow/ARTiledImageView.git DZNWebViewController: :commit: 9121386901af95072fb19eaa3df7060ca46fc337 :git: https://github.com/orta/DZNWebViewController.git @@ -160,7 +154,7 @@ SPEC CHECKSUMS: Alamofire: 7682d43245de14874acd142ec137b144aa1dd335 ARAnalytics: 5468652928cee7100dd0e6b5162631409da37106 ARCollectionViewMasonryLayout: 164b82010cf8ec99bc7a38cffe59a179d7e5a116 - ARTiledImageView: a747cff42142ca04d1dc5cee516186f10b6c0949 + ARTiledImageView: 8c6fd11d9e8459a853d94394adea3a5e8c9329ba Artsy+UIColors: 7a4c987885a8d8da3e22672a3232761f929dd2b6 Artsy+UIFonts: c51bb3b5cbf9c1a5fe198b4385d49133c5c71f5a Artsy+UILabels: 43da757ee01bce8165ec83123eccccdf1fe8843c @@ -175,16 +169,16 @@ SPEC CHECKSUMS: Forgeries: 64ced144ea8341d89a7eec9d1d7986f0f1366250 HockeySDK-Source: d03f7d267d78980af33fb63ddea5199f5bf84049 ISO8601DateFormatter: ab926648eebe497f4d167c0fd083992f959f1274 - Keys: 7d2ff6ff42f6903358267ce56e9392f83c31acfe + Keys: 9c35bf00f612ee1d48556f4a4b9b4551e224e90f Mixpanel: fd52e097ae0d27295d0595b2261c60b94d191fda Moya: 920d40f738e6f4ece84010cec701bb9693be8251 Nimble: 56fc9f5020effa2206de22c3dd910f4fb011b92f Nimble-Snapshots: 2da4d2ec829b458d6886c06812c52493fce284c9 NJKWebViewProgress: f481fd424cb5ecc27eacae11b5397db92fba9a4d NSObject+Rx: 2a9cd801d9c847e6d2486cbad8d7701b67834e70 - ORStackView: 5df6b1b990b0648d8ef6f69f89361ea8648e8f64 + ORStackView: 5836e207304ed62db4177b707f19245454943705 Quick: 5d290df1c69d5ee2f0729956dcf0fd9a30447eaa - Reachability: 2fc2badcac191cd1a15044bc7fe45aa96c499ece + Reachability: 5b69def55ffa04d6f9425a6da1f47338b60557b2 Result: 1b3e431f37cbcd3ad89c6aa9ab0ae55515fae3b6 RxBlocking: 75dad8ed874b732da94780fdf7cf48eabf0d5d2b RxCocoa: 1dd105d8a4e18034ccd7bd6d136445363ce67c8e @@ -197,4 +191,6 @@ SPEC CHECKSUMS: UIView+BooleanAnimations: a760be9a066036e55f298b7b7350a6cb14cfcd97 XNGMarkdownParser: 562999edfa0e0913dc345f0931bdd78be59f826e -COCOAPODS: 0.39.0 +PODFILE CHECKSUM: da7de32d81a4e283f5f6cc3e3e9761774eef0dd1 + +COCOAPODS: 1.1.0.rc.2 From 2ffc694e86713a8af2cf971c8b9cdf2fa6b2f88d Mon Sep 17 00:00:00 2001 From: Ash Furrow Date: Tue, 4 Oct 2016 19:04:32 -0400 Subject: [PATCH 04/46] [Swift 3] More fixes. --- Kiosk.xcodeproj/project.pbxproj | 64 +++++++++---------- Kiosk/Admin/ChooseAuctionViewController.swift | 11 ++-- Kiosk/App/APIPingManager.swift | 2 +- Kiosk/App/AppDelegate+GlobalActions.swift | 2 +- Kiosk/App/AppViewController.swift | 2 +- Kiosk/App/GlobalFunctions.swift | 4 +- Kiosk/App/Models/SystemTime.swift | 7 +- Kiosk/App/Networking/ArtsyAPI.swift | 16 ++--- Kiosk/App/Networking/NetworkLogger.swift | 12 ++-- Kiosk/App/Networking/Networking.swift | 22 +++---- Kiosk/App/SwiftExtensions.swift | 25 ++++++-- .../App/UIViewSubclassesErrorExtensions.swift | 4 +- Kiosk/App/Views/SwitchView.swift | 12 ++-- .../ListingsViewController.swift | 18 +++--- .../Auction Listings/ListingsViewModel.swift | 56 ++++++++-------- .../BidCheckingNetworkModel.swift | 4 +- .../BidDetailsPreviewView.swift | 12 ++-- .../Bid Fulfillment/BidderNetworkModel.swift | 54 ++++++++-------- ...nfirmYourBidArtsyLoginViewController.swift | 2 +- ...mYourBidEnterYourEmailViewController.swift | 8 +-- .../ConfirmYourBidPINViewController.swift | 2 +- ...ConfirmYourBidPasswordViewController.swift | 2 +- .../ConfirmYourBidViewController.swift | 2 +- .../FulfillmentContainerViewController.swift | 2 +- Kiosk/Bid Fulfillment/KeypadViewModel.swift | 2 +- .../LoadingViewController.swift | 18 +++--- .../ManualCreditCardInputViewController.swift | 2 +- Kiosk/Bid Fulfillment/Models/BidDetails.swift | 4 +- .../Models/RegistrationCoordinator.swift | 14 ++-- .../PlaceBidViewController.swift | 6 +- .../RegisterViewController.swift | 2 +- .../RegistrationEmailViewController.swift | 6 +- .../RegistrationMobileViewController.swift | 2 +- .../RegistrationPasswordViewController.swift | 2 +- .../RegistrationPostalZipViewController.swift | 2 +- .../SwipeCreditCardViewController.swift | 4 +- .../YourBiddingDetailsViewController.swift | 9 +-- Kiosk/Help/HelpViewController.swift | 6 +- Kiosk/Observable+JSONAble.swift | 14 ++-- Kiosk/Observable+Operators.swift | 15 +++-- .../SaleArtworkDetailsViewController.swift | 10 +-- Kiosk/Supporting Files/PodsBridgingHeader.h | 4 -- Kiosk/UIKit+Rx.swift | 2 +- KioskTests/ListingsViewControllerTests.swift | 2 +- KioskTests/ListingsViewModelTests.swift | 2 +- Podfile | 5 +- Podfile.lock | 13 ++-- 47 files changed, 251 insertions(+), 240 deletions(-) diff --git a/Kiosk.xcodeproj/project.pbxproj b/Kiosk.xcodeproj/project.pbxproj index 3e24cb54..d0efcd3c 100644 --- a/Kiosk.xcodeproj/project.pbxproj +++ b/Kiosk.xcodeproj/project.pbxproj @@ -221,8 +221,8 @@ 5EFA709C1A9674BD0082C916 /* BidderDetailsRetrieval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EFA709B1A9674BD0082C916 /* BidderDetailsRetrieval.swift */; }; 5EFFE24E1B977DD3006BF64C /* SaleArtworkViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EFFE24D1B977DD3006BF64C /* SaleArtworkViewModel.swift */; }; 5EFFE2501B978C3C006BF64C /* ListingsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EFFE24F1B978C3C006BF64C /* ListingsViewModelTests.swift */; }; - D3F545A8B36E79DF2867D06B /* Pods_KioskTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A30D47B7655B0A0152E7FC55 /* Pods_KioskTests.framework */; }; E70556731BC40BFB0064ABF8 /* MyBidPosition.json in Resources */ = {isa = PBXBuildFile; fileRef = E70556721BC40BFB0064ABF8 /* MyBidPosition.json */; }; + FC356C56217750D6722DBF34 /* Pods_KioskTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 94D0A1FE57C6B14D52392DF4 /* Pods_KioskTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -236,6 +236,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 276814418ACEF21BC773CB08 /* Pods-KioskTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KioskTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-KioskTests/Pods-KioskTests.debug.xcconfig"; sourceTree = ""; }; 5E0A4E3D1B41C9C800CB2BEA /* UIView+LongPressDisplayMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UIView+LongPressDisplayMessage.swift"; path = "Kiosk/UIView+LongPressDisplayMessage.swift"; sourceTree = SOURCE_ROOT; }; 5E19E64E1A65630200F8CBDD /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; 5E19E6511A656CCF00F8CBDD /* LoggerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggerTests.swift; sourceTree = ""; }; @@ -437,11 +438,10 @@ 5EFA709B1A9674BD0082C916 /* BidderDetailsRetrieval.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BidderDetailsRetrieval.swift; sourceTree = ""; }; 5EFFE24D1B977DD3006BF64C /* SaleArtworkViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SaleArtworkViewModel.swift; sourceTree = ""; }; 5EFFE24F1B978C3C006BF64C /* ListingsViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListingsViewModelTests.swift; sourceTree = ""; }; - 871AC5A32DB43C035EC4933D /* Pods-KioskTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KioskTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-KioskTests/Pods-KioskTests.release.xcconfig"; sourceTree = ""; }; - A30D47B7655B0A0152E7FC55 /* Pods_KioskTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_KioskTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 620BB28AC1DE3936837D2731 /* Pods-KioskTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KioskTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-KioskTests/Pods-KioskTests.release.xcconfig"; sourceTree = ""; }; + 94D0A1FE57C6B14D52392DF4 /* Pods_KioskTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_KioskTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B5DFB9BD12B5173B74D620D7 /* Pods.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E70556721BC40BFB0064ABF8 /* MyBidPosition.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = MyBidPosition.json; sourceTree = ""; }; - F36F2D0360FAFC13067DE7EE /* Pods-KioskTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KioskTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-KioskTests/Pods-KioskTests.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -456,22 +456,13 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - D3F545A8B36E79DF2867D06B /* Pods_KioskTests.framework in Frameworks */, + FC356C56217750D6722DBF34 /* Pods_KioskTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 5804E77C62062C5C2F916D18 /* Pods */ = { - isa = PBXGroup; - children = ( - F36F2D0360FAFC13067DE7EE /* Pods-KioskTests.debug.xcconfig */, - 871AC5A32DB43C035EC4933D /* Pods-KioskTests.release.xcconfig */, - ); - name = Pods; - sourceTree = ""; - }; 5E860FC21A5C429C00456E41 /* Admin */ = { isa = PBXGroup; children = ( @@ -773,7 +764,7 @@ 5EB0B7051A5C3E6800B7CBF2 /* KioskTests */, 5EB0B6EE1A5C3E6800B7CBF2 /* Products */, EDE935AE93E27C7CD4C012CE /* Frameworks */, - 5804E77C62062C5C2F916D18 /* Pods */, + FE9B30514E47E14B3751A34A /* Pods */, ); sourceTree = ""; }; @@ -886,11 +877,20 @@ isa = PBXGroup; children = ( B5DFB9BD12B5173B74D620D7 /* Pods.framework */, - A30D47B7655B0A0152E7FC55 /* Pods_KioskTests.framework */, + 94D0A1FE57C6B14D52392DF4 /* Pods_KioskTests.framework */, ); name = Frameworks; sourceTree = ""; }; + FE9B30514E47E14B3751A34A /* Pods */ = { + isa = PBXGroup; + children = ( + 276814418ACEF21BC773CB08 /* Pods-KioskTests.debug.xcconfig */, + 620BB28AC1DE3936837D2731 /* Pods-KioskTests.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -915,12 +915,12 @@ isa = PBXNativeTarget; buildConfigurationList = 5EB0B70F1A5C3E6800B7CBF2 /* Build configuration list for PBXNativeTarget "KioskTests" */; buildPhases = ( - 3713EC04F59C7386009C49D6 /* [CP] Check Pods Manifest.lock */, + 22C6B74474A2FC89BCBEA1C5 /* [CP] Check Pods Manifest.lock */, 5EB0B6FE1A5C3E6800B7CBF2 /* Sources */, 5EB0B6FF1A5C3E6800B7CBF2 /* Frameworks */, 5EB0B7001A5C3E6800B7CBF2 /* Resources */, - 8F886D2A54F4ECF13F03A562 /* [CP] Embed Pods Frameworks */, - EBD00B3B35AA6E619C9CDB8B /* [CP] Copy Pods Resources */, + 0F1DDDE620287465CADB7C17 /* [CP] Embed Pods Frameworks */, + 4D2169D120D205285B5A661F /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -1046,37 +1046,37 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 3713EC04F59C7386009C49D6 /* [CP] Check Pods Manifest.lock */ = { + 0F1DDDE620287465CADB7C17 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "[CP] Check Pods Manifest.lock"; + name = "[CP] Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-KioskTests/Pods-KioskTests-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 8F886D2A54F4ECF13F03A562 /* [CP] Embed Pods Frameworks */ = { + 22C6B74474A2FC89BCBEA1C5 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "[CP] Embed Pods Frameworks"; + name = "[CP] Check Pods Manifest.lock"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-KioskTests/Pods-KioskTests-frameworks.sh\"\n"; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; - EBD00B3B35AA6E619C9CDB8B /* [CP] Copy Pods Resources */ = { + 4D2169D120D205285B5A661F /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1315,7 +1315,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.1; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -1352,7 +1352,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.1; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = 2; @@ -1376,7 +1376,6 @@ "$(SRCROOT)/Pods/CardFlight/**", ); INFOPLIST_FILE = "Kiosk/Supporting Files/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; PRODUCT_BUNDLE_IDENTIFIER = net.artsy.kiosk.beta; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Kiosk/Supporting Files/BridgingHeader.h"; @@ -1402,7 +1401,6 @@ "$(SRCROOT)/Pods/CardFlight/**", ); INFOPLIST_FILE = "Kiosk/Supporting Files/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; PRODUCT_BUNDLE_IDENTIFIER = net.artsy.kiosk.beta; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = "$(PROFILE_UDID)"; @@ -1415,7 +1413,7 @@ }; 5EB0B7101A5C3E6800B7CBF2 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = F36F2D0360FAFC13067DE7EE /* Pods-KioskTests.debug.xcconfig */; + baseConfigurationReference = 276814418ACEF21BC773CB08 /* Pods-KioskTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ENABLE_MODULES = YES; @@ -1430,7 +1428,6 @@ "$(SRCROOT)/Pods/CardFlight/**", ); INFOPLIST_FILE = KioskTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; PRODUCT_BUNDLE_IDENTIFIER = "net.artsy.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -1441,7 +1438,7 @@ }; 5EB0B7111A5C3E6800B7CBF2 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 871AC5A32DB43C035EC4933D /* Pods-KioskTests.release.xcconfig */; + baseConfigurationReference = 620BB28AC1DE3936837D2731 /* Pods-KioskTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ENABLE_MODULES = YES; @@ -1452,7 +1449,6 @@ "$(SRCROOT)/Pods/CardFlight/**", ); INFOPLIST_FILE = KioskTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; PRODUCT_BUNDLE_IDENTIFIER = "net.artsy.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; diff --git a/Kiosk/Admin/ChooseAuctionViewController.swift b/Kiosk/Admin/ChooseAuctionViewController.swift index d86c2023..b537fb3f 100644 --- a/Kiosk/Admin/ChooseAuctionViewController.swift +++ b/Kiosk/Admin/ChooseAuctionViewController.swift @@ -1,5 +1,6 @@ import UIKit import ORStackView +import FLKAutoLayout import Artsy_UIFonts import Artsy_UIButtons @@ -10,7 +11,7 @@ class ChooseAuctionViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - stackScrollView.backgroundColor = .whiteColor() + stackScrollView.backgroundColor = .white stackScrollView.bottomMarginHeight = CGFloat(NSNotFound) stackScrollView.updateConstraints() @@ -19,7 +20,7 @@ class ChooseAuctionViewController: UIViewController { provider.request(endpoint) .filterSuccessfulStatusCodes() .mapJSON() - .mapToObjectArray(Sale.self) + .mapTo(arrayOf: Sale.self) .subscribeNext { activeSales in self.auctions = activeSales @@ -28,10 +29,10 @@ class ChooseAuctionViewController: UIViewController { let title = " \(sale.name) - #\(sale.auctionState) - \(sale.artworkCount)" let button = ARFlatButton() - button.setTitle(title, forState: .Normal) - button.setTitleColor(.blackColor(), forState: .Normal) + button.setTitle(title, forState: .normal) + button.setTitleColor(.blackColor(), forState: .normal) button.tag = i - button.rx_tap.subscribeNext { (_) in + button.rx.tap.subscribeNext { (_) in let defaults = NSUserDefaults.standardUserDefaults() defaults.setObject(sale.id, forKey: "KioskAuctionID") defaults.synchronize() diff --git a/Kiosk/App/APIPingManager.swift b/Kiosk/App/APIPingManager.swift index b2faba30..b50c8701 100644 --- a/Kiosk/App/APIPingManager.swift +++ b/Kiosk/App/APIPingManager.swift @@ -19,6 +19,6 @@ class APIPingManager { } fileprivate func ping() -> Observable { - return provider.request(ArtsyAPI.Ping).map(responseIsOK) + return provider.request(ArtsyAPI.ping).map(responseIsOK) } } diff --git a/Kiosk/App/AppDelegate+GlobalActions.swift b/Kiosk/App/AppDelegate+GlobalActions.swift index db8fb296..f348a1b4 100644 --- a/Kiosk/App/AppDelegate+GlobalActions.swift +++ b/Kiosk/App/AppDelegate+GlobalActions.swift @@ -136,7 +136,7 @@ private extension AppDelegate { if let internalNav: FulfillmentNavigationController = containerController.internalNavigationController() { internalNav.auctionID = self.appViewController.auctionID - let registerVC = storyboard.viewControllerWithID(.RegisterAnAccount) as! RegisterViewController + let registerVC = storyboard.viewController(withID: .RegisterAnAccount) as! RegisterViewController registerVC.placingBid = false registerVC.provider = self.provider internalNav.auctionID = self.appViewController.auctionID diff --git a/Kiosk/App/AppViewController.swift b/Kiosk/App/AppViewController.swift index bd0b4479..ea37a9db 100644 --- a/Kiosk/App/AppViewController.swift +++ b/Kiosk/App/AppViewController.swift @@ -29,7 +29,7 @@ class AppViewController: UIViewController, UINavigationControllerDelegate { } class func instantiate(from storyboard: UIStoryboard) -> AppViewController { - return storyboard.viewControllerWithID(.AppViewController) as! AppViewController + return storyboard.viewController(withID: .AppViewController) as! AppViewController } var sale = Variable(Sale(id: "", name: "", isAuction: true, startDate: Date(), endDate: Date(), artworkCount: 0, state: "")) diff --git a/Kiosk/App/GlobalFunctions.swift b/Kiosk/App/GlobalFunctions.swift index e610e6bc..e8c35680 100644 --- a/Kiosk/App/GlobalFunctions.swift +++ b/Kiosk/App/GlobalFunctions.swift @@ -62,11 +62,11 @@ private class ReachabilityManager: NSObject { } reachability?.startNotifier() - _reach.onNext(reachability.isReachable()) + _reach.onNext(reachability?.isReachable() ?? false) } } -func bindingErrorToInterface(_ error: Error) { +func bindingErrorToInterface(_ error: Swift.Error) { let error = "Binding error to UI: \(error)" #if DEBUG fatalError(error) diff --git a/Kiosk/App/Models/SystemTime.swift b/Kiosk/App/Models/SystemTime.swift index 8880df49..b5f86c80 100644 --- a/Kiosk/App/Models/SystemTime.swift +++ b/Kiosk/App/Models/SystemTime.swift @@ -1,5 +1,4 @@ import Foundation -import ISO8601DateFormatter import RxSwift class SystemTime { @@ -17,8 +16,10 @@ class SystemTime { guard let dictionary = response as? NSDictionary else { return } let formatter = ISO8601DateFormatter() - let artsyDate = formatter.dateFromString(dictionary["iso8601"] as! String?) - self?.systemTimeInterval = NSDate().timeIntervalSinceDate(artsyDate) + let timestamp: String = (dictionary["iso8601"] as? String) ?? "" + if let artsyDate = formatter.date(from: timestamp) { + self?.systemTimeInterval = NSDate().timeIntervalSince(artsyDate) + } }.logError().map(void) } diff --git a/Kiosk/App/Networking/ArtsyAPI.swift b/Kiosk/App/Networking/ArtsyAPI.swift index cceef650..bd3a62ec 100644 --- a/Kiosk/App/Networking/ArtsyAPI.swift +++ b/Kiosk/App/Networking/ArtsyAPI.swift @@ -49,8 +49,8 @@ enum ArtsyAuthenticatedAPI { } extension ArtsyAPI : TargetType, ArtsyAPIType { - public var parameters: [String : Any]? { - <#code#> + public var task: Task { + return .request } var path: String { @@ -113,7 +113,7 @@ extension ArtsyAPI : TargetType, ArtsyAPIType { var base: String { return AppSetup.sharedState.useStaging ? "https://stagingapi.artsy.net" : "https://api.artsy.net" } var baseURL: URL { return URL(string: base)! } - var parameters: [String: AnyObject]? { + var parameters: [String: Any]? { switch self { case .xAuth(let email, let password): @@ -139,7 +139,7 @@ extension ArtsyAPI : TargetType, ArtsyAPIType { return [ "email": email as AnyObject, "password": password as AnyObject, "phone": phone as AnyObject, "name": name as AnyObject, - "location": [ "postal_code": postCode ] + "location": [ "postal_code": postCode ] as AnyObject ] case .bidderDetailsNotification(let auctionID, let identifier): @@ -247,8 +247,8 @@ extension ArtsyAPI : TargetType, ArtsyAPIType { } extension ArtsyAuthenticatedAPI: TargetType, ArtsyAPIType { - public var parameters: [String : Any]? { - <#code#> + public var task: Task { + return .request } var path: String { @@ -292,7 +292,7 @@ extension ArtsyAuthenticatedAPI: TargetType, ArtsyAPIType { var base: String { return AppSetup.sharedState.useStaging ? "https://stagingapi.artsy.net" : "https://api.artsy.net" } var baseURL: URL { return URL(string: base)! } - var parameters: [String: AnyObject]? { + var parameters: [String: Any]? { switch self { case .registerToBid(let auctionID): @@ -402,5 +402,5 @@ private extension String { } func url(_ route: TargetType) -> String { - return route.baseURL.URLByAppendingPathComponent(route.path).absoluteString + return route.baseURL.appendingPathComponent(route.path).absoluteString } diff --git a/Kiosk/App/Networking/NetworkLogger.swift b/Kiosk/App/Networking/NetworkLogger.swift index c7bebfa4..17167ba7 100644 --- a/Kiosk/App/Networking/NetworkLogger.swift +++ b/Kiosk/App/Networking/NetworkLogger.swift @@ -10,7 +10,7 @@ class NetworkLogger: PluginType { let whitelist: Comparison let blacklist: Comparison - init(whitelist: Comparison = { _ -> Bool in return true }, blacklist: Comparison = { _ -> Bool in return true }) { + init(whitelist: @escaping Comparison = { _ -> Bool in return true }, blacklist: @escaping Comparison = { _ -> Bool in return true }) { self.whitelist = whitelist self.blacklist = blacklist } @@ -18,7 +18,7 @@ class NetworkLogger: PluginType { func willSendRequest(_ request: RequestType, target: TargetType) { // If the target is in the blacklist, don't log it. guard blacklist(target) == false else { return } - logger.log("Sending request: \(request.request?.URL?.absoluteString ?? String())") + logger.log("Sending request: \(request.request?.url?.absoluteString ?? String())") } func didReceiveResponse(_ result: Result, target: TargetType) { @@ -26,12 +26,12 @@ class NetworkLogger: PluginType { guard blacklist(target) == false else { return } switch result { - case .Success(let response): - if 200..<400 ~= (response.statusCode ?? 0) && whitelist(target) == false { + case .success(let response): + if 200..<400 ~= (response.statusCode ) && whitelist(target) == false { // If the status code is OK, and if it's not in our whitelist, then don't worry about logging its response body. - logger.log("Received response(\(response.statusCode ?? 0)) from \(response.response?.URL?.absoluteString ?? String()).") + logger.log("Received response(\(response.statusCode )) from \(response.response?.url?.absoluteString ?? String()).") } - case .Failure(let error): + case .failure(let error): // Otherwise, log everything. logger.log("Received networking error: \(error)") } diff --git a/Kiosk/App/Networking/Networking.swift b/Kiosk/App/Networking/Networking.swift index c29c65a4..6c84c0d8 100644 --- a/Kiosk/App/Networking/Networking.swift +++ b/Kiosk/App/Networking/Networking.swift @@ -1,5 +1,4 @@ import Foundation -import ISO8601DateFormatter import Moya import RxSwift import Alamofire @@ -8,15 +7,16 @@ class OnlineProvider: RxMoyaProvider where Target: TargetType { fileprivate let online: Observable - init(endpointClosure: @escaping MoyaProvider.EndpointClosure = MoyaProvider.DefaultEndpointMapping, - requestClosure: @escaping MoyaProvider.RequestClosure = MoyaProvider.DefaultRequestMapping, - stubClosure: @escaping MoyaProvider.StubClosure = MoyaProvider.NeverStub, - manager: Manager = Alamofire.Manager.sharedInstance, + init(endpointClosure: @escaping EndpointClosure = MoyaProvider.DefaultEndpointMapping, + requestClosure: @escaping RequestClosure = MoyaProvider.DefaultRequestMapping, + stubClosure: @escaping StubClosure = MoyaProvider.NeverStub, + manager: Manager = RxMoyaProvider.DefaultAlamofireManager(), plugins: [PluginType] = [], + trackInflights: Bool = false, online: Observable = connectedToInternetOrStubbing()) { - self.online = online - super.init(endpointClosure: endpointClosure, requestClosure: requestClosure, stubClosure: stubClosure, manager: manager, plugins: plugins) + self.online = online + super.init(endpointClosure: endpointClosure, requestClosure: requestClosure, stubClosure: stubClosure, manager: manager, plugins: plugins, trackInflights: trackInflights) } override func request(_ token: Target) -> Observable { @@ -66,13 +66,13 @@ private extension Networking { return (token: dictionary["xapp_token"] as? String, expiry: dictionary["expires_in"] as? String) } - .do { event in - guard case Event.next(let element) = event else { return } + .doOn { event in + guard case RxSwift.Event.next(let element) = event else { return } - let formatter = ISO8601DateFormatter.ISO8601DateFormatter() + let formatter = ISO8601DateFormatter() // These two lines set the defaults values injected into appToken appToken.token = element.0 - appToken.expiry = formatter.dateFromString(element.1) + appToken.expiry = formatter.date(from: element.1 ?? "") } .map { (token, expiry) -> String? in return token diff --git a/Kiosk/App/SwiftExtensions.swift b/Kiosk/App/SwiftExtensions.swift index d64dd49b..a3dbeba7 100644 --- a/Kiosk/App/SwiftExtensions.swift +++ b/Kiosk/App/SwiftExtensions.swift @@ -61,12 +61,25 @@ import Moya extension Moya.Error { var response: Moya.Response? { switch self { - case .ImageMapping(let response): return response - case .JSONMapping(let response): return response - case .StringMapping(let response): return response - case .StatusCode(let response): return response - case .Data(let response): return response - case .Underlying: return nil + case .imageMapping(let response): return response + case .jsonMapping(let response): return response + case .stringMapping(let response): return response + case .statusCode(let response): return response + case .data(let response): return response + case .underlying: return nil } } } + +//// TODO: This extension shouldn't be necessary. +//import ORStackView +//extension ORStackView { +// var bottomMarginHeight: CGFloat { +// get { +// return self.lastMarginHeight +// } +// set { +// self.lastMarginHeight = newValue +// } +// } +//} diff --git a/Kiosk/App/UIViewSubclassesErrorExtensions.swift b/Kiosk/App/UIViewSubclassesErrorExtensions.swift index b1da2841..a2f8bc0c 100644 --- a/Kiosk/App/UIViewSubclassesErrorExtensions.swift +++ b/Kiosk/App/UIViewSubclassesErrorExtensions.swift @@ -5,7 +5,7 @@ extension Button { func flashError(_ message:String) { let originalTitle = self.title(for: UIControlState()) - setTitleColor(.white(), for: .disabled) + setTitleColor(.white, for: .disabled) setBackgroundColor(.artsyRed(), for: .disabled, animated: true) setBorderColor(.artsyRed(), for: .disabled, animated: true) @@ -13,7 +13,7 @@ extension Button { delayToMainThread(2) { self.setTitleColor(.artsyMediumGrey(), for: .disabled) - self.setBackgroundColor(.white(), for: .disabled, animated: true) + self.setBackgroundColor(.white, for: .disabled, animated: true) self.setTitle(originalTitle, for: .disabled) self.setBorderColor(.artsyMediumGrey(), for: .disabled, animated: true) } diff --git a/Kiosk/App/Views/SwitchView.swift b/Kiosk/App/Views/SwitchView.swift index 18acc8c2..8ba7e307 100644 --- a/Kiosk/App/Views/SwitchView.swift +++ b/Kiosk/App/Views/SwitchView.swift @@ -31,13 +31,13 @@ class SwitchView: UIView { if let titleLabel = button.titleLabel { titleLabel.font = UIFont.sansSerifFont(withSize: 13) - titleLabel.backgroundColor = .white() + titleLabel.backgroundColor = .white titleLabel.isOpaque = true } - button.backgroundColor = .white() - button.setTitleColor(.black(), for: .disabled) - button.setTitleColor(.black(), for: .selected) + button.backgroundColor = .white + button.setTitleColor(.black, for: .disabled) + button.setTitleColor(.black, for: .selected) button.setTitleColor(.artsyMediumGrey(), for: UIControlState()) return button @@ -115,8 +115,8 @@ private extension SwitchView { selectionIndicator.addSubview(topSelectionIndicator) selectionIndicator.addSubview(bottomSelectionIndicator) - topSelectionIndicator.backgroundColor = .black() - bottomSelectionIndicator.backgroundColor = .black() + topSelectionIndicator.backgroundColor = .black + bottomSelectionIndicator.backgroundColor = .black topSelectionIndicator.alignTop("0", leading: "0", bottom: nil, trailing: "0", to: selectionIndicator) bottomSelectionIndicator.alignTop(nil, leading: "0", bottom: "0", trailing: "0", to: selectionIndicator) diff --git a/Kiosk/Auction Listings/ListingsViewController.swift b/Kiosk/Auction Listings/ListingsViewController.swift index eb0e43f4..e2e5373b 100644 --- a/Kiosk/Auction Listings/ListingsViewController.swift +++ b/Kiosk/Auction Listings/ListingsViewController.swift @@ -30,7 +30,7 @@ class ListingsViewController: UIViewController { return ListingsViewModel(provider: self.provider, selectedIndex: self.switchView.selectedIndex, - showDetails: applyUnowned(self, ListingsViewController.showDetailsForSaleArtwork), + showDetails: applyUnowned(self, ListingsViewController.showDetails), presentModal: applyUnowned(self, ListingsViewController.presentModalForSaleArtwork) ) }() @@ -116,7 +116,7 @@ class ListingsViewController: UIViewController { case true: return ListingsViewController.masonryLayout() default: - return ListingsViewController.tableLayout(width: (self?.switchView.frame ?? CGRectZero).width) + return ListingsViewController.tableLayout(width: (self?.switchView.frame ?? CGRect.zero).width) } } .subscribeNext { [weak self] layout in @@ -149,7 +149,7 @@ class ListingsViewController: UIViewController { extension ListingsViewController { class func instantiateFromStoryboard(_ storyboard: UIStoryboard) -> ListingsViewController { - return storyboard.viewControllerWithID(.AuctionListings) as! ListingsViewController + return storyboard.viewController(withID: .AuctionListings) as! ListingsViewController } } @@ -169,20 +169,20 @@ extension ListingsViewController: UICollectionViewDataSource, UICollectionViewDe listingsCell.downloadImage = downloadImage listingsCell.cancelDownloadImage = cancelDownloadImage - listingsCell.setViewModel(viewModel.saleArtworkViewModelAtIndexPath(indexPath)) + listingsCell.setViewModel(viewModel.saleArtworkViewModel(atIndexPath: indexPath)) let bid = listingsCell.bidPressed.takeUntil(listingsCell.preparingForReuse) let moreInfo = listingsCell.moreInfo.takeUntil(listingsCell.preparingForReuse) bid .subscribeNext { [weak self] _ in - self?.viewModel.presentModalForSaleArtworkAtIndexPath(indexPath) + self?.viewModel.presentModalForSaleArtwork(atIndexPath: indexPath) } .addDisposableTo(rx_disposeBag) moreInfo - .subscribeNext{ [weak self] _ in - self?.viewModel.showDetailsForSaleArtworkAtIndexPath(indexPath) + .subscribeNext { [weak self] _ in + self?.viewModel.showDetailsForSaleArtwork(atIndexPath: indexPath) } .addDisposableTo(rx_disposeBag) } @@ -191,7 +191,7 @@ extension ListingsViewController: UICollectionViewDataSource, UICollectionViewDe } func collectionView(_ collectionView: UICollectionView!, layout collectionViewLayout: ARCollectionViewMasonryLayout!, variableDimensionForItemAt indexPath: IndexPath!) -> CGFloat { - return MasonryCollectionViewCell.heightForCellWithImageAspectRatio(viewModel.imageAspectRatioForSaleArtworkAtIndexPath(indexPath)) + return MasonryCollectionViewCell.heightForCellWithImageAspectRatio(viewModel.imageAspectRatioForSaleArtwork(atIndexPath: indexPath)) } } @@ -237,7 +237,7 @@ extension UICollectionView { class func listingsCollectionViewWithDelegateDatasource(_ delegateDatasource: ListingsViewController) -> UICollectionView { let collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: ListingsViewController.masonryLayout()) - collectionView.backgroundColor = .clear() + collectionView.backgroundColor = .clear collectionView.dataSource = delegateDatasource collectionView.delegate = delegateDatasource collectionView.alwaysBounceVertical = true diff --git a/Kiosk/Auction Listings/ListingsViewModel.swift b/Kiosk/Auction Listings/ListingsViewModel.swift index 52bdb882..55800d32 100644 --- a/Kiosk/Auction Listings/ListingsViewModel.swift +++ b/Kiosk/Auction Listings/ListingsViewModel.swift @@ -16,17 +16,17 @@ protocol ListingsViewModelType { var gridSelected: Observable! { get } var updatedContents: Observable { get } - var scheduleOnBackground: (_ observable: Observable) -> Observable { get } + var scheduleOnBackground: (_ observable: Observable) -> Observable { get } var scheduleOnForeground: (_ observable: Observable<[SaleArtwork]>) -> Observable<[SaleArtwork]> { get } - func saleArtworkViewModelAtIndexPath(_ indexPath: IndexPath) -> SaleArtworkViewModel - func showDetailsForSaleArtworkAtIndexPath(_ indexPath: IndexPath) - func presentModalForSaleArtworkAtIndexPath(_ indexPath: IndexPath) - func imageAspectRatioForSaleArtworkAtIndexPath(_ indexPath: IndexPath) -> CGFloat? + func saleArtworkViewModel(atIndexPath indexPath: IndexPath) -> SaleArtworkViewModel + func showDetailsForSaleArtwork(atIndexPath indexPath: IndexPath) + func presentModalForSaleArtwork(atIndexPath indexPath: IndexPath) + func imageAspectRatioForSaleArtwork(atIndexPath indexPath: IndexPath) -> CGFloat? } // Cheating here, should be in the instance but there's only ever one instance, so ¯\_(ツ)_/¯ -private let backgroundScheduler = SerialDispatchQueueScheduler(globalConcurrentQueueQOS: .Default) +private let backgroundScheduler = SerialDispatchQueueScheduler(globalConcurrentQueueQOS: .default) class ListingsViewModel: NSObject, ListingsViewModelType { @@ -38,7 +38,7 @@ class ListingsViewModel: NSObject, ListingsViewModelType { let pageSize: Int let syncInterval: TimeInterval let logSync: (Date) -> Void - var scheduleOnBackground: (_ observable: Observable) -> Observable + var scheduleOnBackground: (_ observable: Observable) -> Observable var scheduleOnForeground: (_ observable: Observable<[SaleArtwork]>) -> Observable<[SaleArtwork]> var numberOfSaleArtworks: Int { @@ -51,7 +51,7 @@ class ListingsViewModel: NSObject, ListingsViewModelType { return sortedSaleArtworks .asObservable() .map { $0.count > 0 } - .ignore(false) + .ignore(value: false) .map { _ in NSDate() } } @@ -61,13 +61,13 @@ class ListingsViewModel: NSObject, ListingsViewModelType { init(provider: Networking, selectedIndex: Observable, - showDetails: ShowDetailsClosure, - presentModal: PresentModalClosure, + showDetails: @escaping ShowDetailsClosure, + presentModal: @escaping PresentModalClosure, pageSize: Int = 10, syncInterval: TimeInterval = SyncInterval, - logSync:(Date) -> Void = ListingsViewModel.DefaultLogging, - scheduleOnBackground: (_ observable: Observable) -> Observable = ListingsViewModel.DefaultScheduler(onBackground: true), - scheduleOnForeground: (_ observable: Observable<[SaleArtwork]>) -> Observable<[SaleArtwork]> = ListingsViewModel.DefaultScheduler(onBackground: false), + logSync:@escaping (Date) -> Void = ListingsViewModel.DefaultLogging, + scheduleOnBackground: @escaping (_ observable: Observable) -> Observable = ListingsViewModel.DefaultScheduler(onBackground: true), + scheduleOnForeground: @escaping (_ observable: Observable<[SaleArtwork]>) -> Observable<[SaleArtwork]> = ListingsViewModel.DefaultScheduler(onBackground: false), auctionID: String = AppSetup.sharedState.auctionID) { self.provider = provider @@ -90,7 +90,7 @@ class ListingsViewModel: NSObject, ListingsViewModelType { fileprivate func setup(_ selectedIndex: Observable) { recurringListingsRequest() - .takeUntil(rx_deallocated) + .takeUntil(rx.deallocated) .bindTo(saleArtworks) .addDisposableTo(rx_disposeBag) @@ -98,14 +98,14 @@ class ListingsViewModel: NSObject, ListingsViewModelType { return sortedSaleArtworks.count == 0 } - gridSelected = selectedIndex.map { ListingsViewModel.SwitchValues(rawValue: $0) == .Some(.Grid) } + gridSelected = selectedIndex.map { ListingsViewModel.SwitchValues(rawValue: $0) == .some(.grid) } let distinctSaleArtworks = saleArtworks .asObservable() .distinctUntilChanged { (lhs, rhs) -> Bool in return lhs == rhs } - .mapReplace(0) // To use in combineLatest, we must have an array of identically-typed observables. + .mapReplace(with: 0) // To use in combineLatest, we must have an array of identically-typed observables. [selectedIndex, distinctSaleArtworks] .combineLatest { ints in @@ -126,21 +126,21 @@ class ListingsViewModel: NSObject, ListingsViewModelType { } - fileprivate func listingsRequest(forPage page: Int) -> Observable { - return provider.request(.AuctionListings(id: auctionID, page: page, pageSize: self.pageSize)).filterSuccessfulStatusCodes().mapJSON() + fileprivate func listingsRequest(forPage page: Int) -> Observable { + return provider.request(.auctionListings(id: auctionID, page: page, pageSize: self.pageSize)).filterSuccessfulStatusCodes().mapJSON() } // Repeatedly calls itself with page+1 until the count of the returned array is < pageSize. - fileprivate func retrieveAllListingsRequest(_ page: Int) -> Observable { + fileprivate func retrieveAllListingsRequest(_ page: Int) -> Observable { return Observable.create { [weak self] observer in guard let me = self else { return NopDisposable.instance } - return me.listingsRequestForPage(page).subscribeNext { object in + return me.listingsRequest(forPage: page).subscribeNext { object in guard let array = object as? Array else { return } guard let me = self else { return } // This'll either be the next page request or .empty. - let nextPage: Observable + let nextPage: Observable // We must have more results to retrieve if array.count >= me.pageSize { @@ -149,7 +149,7 @@ class ListingsViewModel: NSObject, ListingsViewModelType { nextPage = .empty() } - Observable.just(object) + Observable.just(object) .concat(nextPage) .subscribe(observer) } @@ -158,23 +158,23 @@ class ListingsViewModel: NSObject, ListingsViewModelType { // Fetches all pages of the auction fileprivate func allListingsRequest() -> Observable<[SaleArtwork]> { - let backgroundJSONParsing = scheduleOnBackground(observable: retrieveAllListingsRequest(1)).reduce([AnyObject]()) + let backgroundJSONParsing = scheduleOnBackground(retrieveAllListingsRequest(1)).reduce([Any]()) { (memo, object) in - guard let array = object as? Array else { return memo } + guard let array = object as? Array else { return memo } return memo + array } - .mapToObjectArray(SaleArtwork) - .logServerError("Sale artworks failed to retrieve+parse") + .mapTo(arrayOf: SaleArtwork.self) + .logServerError(message: "Sale artworks failed to retrieve+parse") .catchErrorJustReturn([]) - return scheduleOnForeground(observable: backgroundJSONParsing) + return scheduleOnForeground(backgroundJSONParsing) } fileprivate func recurringListingsRequest() -> Observable> { let recurring = Observable.interval(syncInterval, scheduler: MainScheduler.instance) .map { _ in Date() } .startWith(Date()) - .takeUntil(rx_deallocating) + .takeUntil(rx.deallocated) return recurring diff --git a/Kiosk/Bid Fulfillment/BidCheckingNetworkModel.swift b/Kiosk/Bid Fulfillment/BidCheckingNetworkModel.swift index 305bf954..5733db1b 100644 --- a/Kiosk/Bid Fulfillment/BidCheckingNetworkModel.swift +++ b/Kiosk/Bid Fulfillment/BidCheckingNetworkModel.swift @@ -53,7 +53,7 @@ class BidCheckingNetworkModel: NSObject, BidCheckingNetworkModelType { self.bidDetails.saleArtwork?.updateWithValues(saleArtwork) self.reserveNotMet.value = ReserveStatus.initOrDefault(saleArtwork.reserveStatus).reserveNotMet - return .just() + return .just(Void()) } .doOnError { _ in logger.log("Bidder position was processed but corresponding saleArtwork was not found") @@ -78,7 +78,7 @@ class BidCheckingNetworkModel: NSObject, BidCheckingNetworkModelType { if let processedAt = bidderPositionObject.processedAt { logger.log("BidPosition finished processing at \(processedAt), proceeding...") - return .just() + return .just(Void()) } else { // The backend hasn't finished processing the bid yet diff --git a/Kiosk/Bid Fulfillment/BidDetailsPreviewView.swift b/Kiosk/Bid Fulfillment/BidDetailsPreviewView.swift index a91d5ed3..df248b7e 100644 --- a/Kiosk/Bid Fulfillment/BidDetailsPreviewView.swift +++ b/Kiosk/Bid Fulfillment/BidDetailsPreviewView.swift @@ -20,7 +20,7 @@ class BidDetailsPreviewView: UIView { dynamic let currentBidPriceLabel = ARSerifLabel() override func awakeFromNib() { - self.backgroundColor = .white() + self.backgroundColor = .white artistNameLabel.numberOfLines = 1 artworkTitleLabel.numberOfLines = 1 @@ -44,7 +44,7 @@ class BidDetailsPreviewView: UIView { artwork .subscribeNext { [weak self] artwork in if let url = artwork?.defaultImage?.thumbnailURL() { - self?.bidDetails?.setImage(url: url, imageView: self!.artworkImageView) + self?.bidDetails?.setImage(url, self!.artworkImageView) } else { self?.artworkImageView.image = nil } @@ -58,7 +58,7 @@ class BidDetailsPreviewView: UIView { .map { name in return name ?? "" } - .bindTo(artistNameLabel.rx_text) + .bindTo(artistNameLabel.rx.text) .addDisposableTo(rx_disposeBag) artwork @@ -70,7 +70,7 @@ class BidDetailsPreviewView: UIView { return artwork.titleAndDate } .mapToOptional() - .bindTo(artworkTitleLabel.rx_attributedText) + .bindTo(artworkTitleLabel.rx.attributedText) .addDisposableTo(rx_disposeBag) _bidDetails @@ -80,9 +80,9 @@ class BidDetailsPreviewView: UIView { .map { bidDetails in guard let cents = bidDetails.bidAmountCents.value else { return "" } - return "Your bid: " + NSNumberFormatter.currencyStringForCents(cents) + return "Your bid: " + NumberFormatter.currencyString(forCents: cents) } - .bindTo(currentBidPriceLabel.rx_text) + .bindTo(currentBidPriceLabel.rx.text) .addDisposableTo(rx_disposeBag) for subview in [artworkImageView, artistNameLabel, artworkTitleLabel, currentBidPriceLabel] { diff --git a/Kiosk/Bid Fulfillment/BidderNetworkModel.swift b/Kiosk/Bid Fulfillment/BidderNetworkModel.swift index 01e193a5..f1050edf 100644 --- a/Kiosk/Bid Fulfillment/BidderNetworkModel.swift +++ b/Kiosk/Bid Fulfillment/BidderNetworkModel.swift @@ -29,10 +29,10 @@ class BidderNetworkModel: NSObject, BidderNetworkModelType { func createOrGetBidder() -> Observable { return createOrUpdateUser() .flatMap { provider -> Observable in - return self.createOrUpdateBidder(provider).mapReplace(provider) + return self.createOrUpdateBidder(provider: provider).mapReplace(with: provider) } .flatMap { provider -> Observable in - self.getMyPaddleNumber(provider).mapReplace(provider) + self.getMyPaddleNumber(provider: provider).mapReplace(with: provider) } } } @@ -42,7 +42,7 @@ private extension BidderNetworkModel { // MARK: - Chained observables func checkUserEmailExists(_ email: String) -> Observable { - let request = provider.request(.FindExistingEmailRegistration(email: email)) + let request = provider.request(.findExistingEmailRegistration(email: email)) return request.map { response in return response.statusCode != 404 @@ -63,13 +63,13 @@ private extension BidderNetworkModel { } } .flatMap { provider -> Observable in - self.addCardToUser(provider).mapReplace(provider) // After update/create observable finishes, add a CC to their account (if we've collected one) + self.addCardToUser(provider: provider).mapReplace(with: provider) // After update/create observable finishes, add a CC to their account (if we've collected one) } } func createNewUser() -> Observable { let newUser = bidDetails.newUser - let endpoint: ArtsyAPI = ArtsyAPI.CreateUser(email: newUser.email.value!, password: newUser.password.value!, phone: newUser.phoneNumber.value!, postCode: newUser.zipCode.value ?? "", name: newUser.name.value ?? "") + let endpoint: ArtsyAPI = ArtsyAPI.createUser(email: newUser.email.value!, password: newUser.password.value!, phone: newUser.phoneNumber.value!, postCode: newUser.zipCode.value ?? "", name: newUser.name.value ?? "") return provider.request(endpoint) .filterSuccessfulStatusCodes() @@ -78,33 +78,33 @@ private extension BidderNetworkModel { logger.log("Creating user failed.") logger.log("Error: \((error as NSError).localizedDescription). \n \((error as NSError).artsyServerError())") }.flatMap { _ -> Observable in - self.bidDetails.authenticatedNetworking(self.provider) + self.bidDetails.authenticatedNetworking(provider: self.provider) } } func updateUser() -> Observable { let newUser = bidDetails.newUser - let endpoint = ArtsyAuthenticatedAPI.UpdateMe(email: newUser.email.value!, phone: newUser.phoneNumber.value!, postCode: newUser.zipCode.value ?? "", name: newUser.name.value ?? "") + let endpoint = ArtsyAuthenticatedAPI.updateMe(email: newUser.email.value!, phone: newUser.phoneNumber.value!, postCode: newUser.zipCode.value ?? "", name: newUser.name.value ?? "") - return bidDetails.authenticatedNetworking(provider) + return bidDetails.authenticatedNetworking(provider: provider) .flatMap { (provider) -> Observable in provider.request(endpoint) .mapJSON() .logNext() - .mapReplace(provider) + .mapReplace(with: provider) } - .logServerError("Updating user failed.") + .logServerError(message: "Updating user failed.") } func addCardToUser(provider: AuthorizedNetworking) -> Observable { // If the user was asked to swipe a card, we'd have stored the token. // If the token is not there, then the user must already have one on file. So we can skip this step. guard let token = bidDetails.newUser.creditCardToken.value else { - return .just() + return .just(Void()) } let swiped = bidDetails.newUser.swipedCreditCard - let endpoint = ArtsyAuthenticatedAPI.RegisterCard(stripeToken: token, swiped: swiped) + let endpoint = ArtsyAuthenticatedAPI.registerCard(stripeToken: token, swiped: swiped) return provider.request(endpoint) .filterSuccessfulStatusCodes() @@ -115,30 +115,30 @@ private extension BidderNetworkModel { self?.bidDetails.newUser.creditCardToken.value = nil } - .logServerError("Adding Card to User failed") + .logServerError(message: "Adding Card to User failed") } // MARK: - Auction / Bidder observables func createOrUpdateBidder(provider: AuthorizedNetworking) -> Observable { - let bool = self.checkForBidderOnAuction(bidDetails.auctionID, provider: provider) + let bool = self.checkForBidderOnAuction(auctionID: bidDetails.auctionID, provider: provider) return bool.flatMap { exists -> Observable in if exists { - return .just() + return .just(Void()) } else { - return self.registerToAuction(self.bidDetails.auctionID, provider: provider).then { [weak self] in self?.generateAPIN(provider) } + return self.register(toAuction: self.bidDetails.auctionID, provider: provider).then { [weak self] in self?.generateAPIN(provider: provider) } } } } func checkForBidderOnAuction(auctionID: String, provider: AuthorizedNetworking) -> Observable { - let endpoint = ArtsyAuthenticatedAPI.MyBiddersForAuction(auctionID: auctionID) + let endpoint = ArtsyAuthenticatedAPI.myBiddersForAuction(auctionID: auctionID) let request = provider.request(endpoint) .filterSuccessfulStatusCodes() .mapJSON() - .mapToObjectArray(Bidder) + .mapTo(arrayOf: Bidder.self) return request.map { [weak self] bidders -> Bool in if let bidder = bidders.first { @@ -149,36 +149,36 @@ private extension BidderNetworkModel { } return false - }.logServerError("Getting user bidders failed.") + }.logServerError(message: "Getting user bidders failed.") } func register(toAuction auctionID: String, provider: AuthorizedNetworking) -> Observable { - let endpoint = ArtsyAuthenticatedAPI.RegisterToBid(auctionID: auctionID) + let endpoint = ArtsyAuthenticatedAPI.registerToBid(auctionID: auctionID) let register = provider.request(endpoint) .filterSuccessfulStatusCodes() .mapJSON() - .mapToObject(Bidder) + .mapTo(object: Bidder.self) return register.doOnNext{ [weak self] bidder in self?.bidDetails.bidderID.value = bidder.id self?.bidDetails.newUser.hasBeenRegistered.value = true } - .logServerError("Registering for Auction Failed.") + .logServerError(message: "Registering for Auction Failed.") .map(void) } func generateAPIN(provider: AuthorizedNetworking) -> Observable { - let endpoint = ArtsyAuthenticatedAPI.CreatePINForBidder(bidderID: bidDetails.bidderID.value!) + let endpoint = ArtsyAuthenticatedAPI.createPINForBidder(bidderID: bidDetails.bidderID.value!) return provider.request(endpoint) .filterSuccessfulStatusCodes() .mapJSON() .doOnNext { [weak self] json in - let pin = json["pin"] as? String + let pin = (json as AnyObject)["pin"] as? String self?.bidDetails.bidderPIN.value = pin } - .logServerError("Generating a PIN for bidder has failed.") + .logServerError(message: "Generating a PIN for bidder has failed.") .map(void) } @@ -187,11 +187,11 @@ private extension BidderNetworkModel { return provider.request(endpoint) .filterSuccessfulStatusCodes() .mapJSON() - .mapToObject(User.self) + .mapTo(object: User.self) .doOnNext { [weak self] user in self?.bidDetails.paddleNumber.value = user.paddleNumber } - .logServerError("Getting Bidder ID failed.") + .logServerError(message: "Getting Bidder ID failed.") .map(void) } } diff --git a/Kiosk/Bid Fulfillment/ConfirmYourBidArtsyLoginViewController.swift b/Kiosk/Bid Fulfillment/ConfirmYourBidArtsyLoginViewController.swift index b9aa8f03..ace5f951 100644 --- a/Kiosk/Bid Fulfillment/ConfirmYourBidArtsyLoginViewController.swift +++ b/Kiosk/Bid Fulfillment/ConfirmYourBidArtsyLoginViewController.swift @@ -20,7 +20,7 @@ class ConfirmYourBidArtsyLoginViewController: UIViewController { var provider: Networking! class func instantiateFromStoryboard(_ storyboard: UIStoryboard) -> ConfirmYourBidArtsyLoginViewController { - return storyboard.viewControllerWithID(.ConfirmYourBidArtsyLogin) as! ConfirmYourBidArtsyLoginViewController + return storyboard.viewController(withID: .ConfirmYourBidArtsyLogin) as! ConfirmYourBidArtsyLoginViewController } override func viewDidLoad() { diff --git a/Kiosk/Bid Fulfillment/ConfirmYourBidEnterYourEmailViewController.swift b/Kiosk/Bid Fulfillment/ConfirmYourBidEnterYourEmailViewController.swift index 643489e6..18a39a2d 100644 --- a/Kiosk/Bid Fulfillment/ConfirmYourBidEnterYourEmailViewController.swift +++ b/Kiosk/Bid Fulfillment/ConfirmYourBidEnterYourEmailViewController.swift @@ -10,7 +10,7 @@ class ConfirmYourBidEnterYourEmailViewController: UIViewController { @IBOutlet var bidDetailsPreviewView: BidDetailsPreviewView! class func instantiateFromStoryboard(_ storyboard: UIStoryboard) -> ConfirmYourBidEnterYourEmailViewController { - return storyboard.viewControllerWithID(.ConfirmYourBidEnterEmail) as! ConfirmYourBidEnterYourEmailViewController + return storyboard.viewController(withID: .ConfirmYourBidEnterEmail) as! ConfirmYourBidEnterYourEmailViewController } var provider: Networking! @@ -18,13 +18,13 @@ class ConfirmYourBidEnterYourEmailViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - let emailText = emailTextField.rx_text + let emailText = emailTextField.rx.textInput.text let inputIsEmail = emailText.map(stringIsEmailAddress) let action = CocoaAction(enabledIf: inputIsEmail) { [weak self] _ in guard let me = self else { return .empty() } - let endpoint: ArtsyAPI = ArtsyAPI.FindExistingEmailRegistration(email: me.emailTextField.text ?? "") + let endpoint: ArtsyAPI = ArtsyAPI.findExistingEmailRegistration(email: me.emailTextField.text ?? "") return self?.provider.request(endpoint) .filterStatusCode(200) @@ -40,7 +40,7 @@ class ConfirmYourBidEnterYourEmailViewController: UIViewController { confirmButton.rx_action = action - let unbind = action.executing.ignore(false) + let unbind = action.executing.ignore(value: false) let nav = self.fulfillmentNav() diff --git a/Kiosk/Bid Fulfillment/ConfirmYourBidPINViewController.swift b/Kiosk/Bid Fulfillment/ConfirmYourBidPINViewController.swift index 8f87860b..f22ebc47 100644 --- a/Kiosk/Bid Fulfillment/ConfirmYourBidPINViewController.swift +++ b/Kiosk/Bid Fulfillment/ConfirmYourBidPINViewController.swift @@ -19,7 +19,7 @@ class ConfirmYourBidPINViewController: UIViewController { // TODO: These all need to be changed. class func instantiateFromStoryboard(_ storyboard: UIStoryboard) -> ConfirmYourBidPINViewController { - return storyboard.viewControllerWithID(.ConfirmYourBidPIN) as! ConfirmYourBidPINViewController + return storyboard.viewController(withID: .ConfirmYourBidPIN) as! ConfirmYourBidPINViewController } override func viewDidLoad() { diff --git a/Kiosk/Bid Fulfillment/ConfirmYourBidPasswordViewController.swift b/Kiosk/Bid Fulfillment/ConfirmYourBidPasswordViewController.swift index 177b805e..2d06caac 100644 --- a/Kiosk/Bid Fulfillment/ConfirmYourBidPasswordViewController.swift +++ b/Kiosk/Bid Fulfillment/ConfirmYourBidPasswordViewController.swift @@ -7,7 +7,7 @@ class ConfirmYourBidPasswordViewController: UIViewController { @IBOutlet var bidDetailsPreviewView: BidDetailsPreviewView! class func instantiateFromStoryboard(_ storyboard: UIStoryboard) -> ConfirmYourBidPasswordViewController { - return storyboard.viewControllerWithID(.ConfirmYourBid) as! ConfirmYourBidPasswordViewController + return storyboard.viewController(withID: .ConfirmYourBid) as! ConfirmYourBidPasswordViewController } override func viewDidLoad() { diff --git a/Kiosk/Bid Fulfillment/ConfirmYourBidViewController.swift b/Kiosk/Bid Fulfillment/ConfirmYourBidViewController.swift index e48752d4..cdef851b 100644 --- a/Kiosk/Bid Fulfillment/ConfirmYourBidViewController.swift +++ b/Kiosk/Bid Fulfillment/ConfirmYourBidViewController.swift @@ -27,7 +27,7 @@ class ConfirmYourBidViewController: UIViewController { var provider: Networking! class func instantiateFromStoryboard(_ storyboard: UIStoryboard) -> ConfirmYourBidViewController { - return storyboard.viewControllerWithID(.ConfirmYourBid) as! ConfirmYourBidViewController + return storyboard.viewController(withID: .ConfirmYourBid) as! ConfirmYourBidViewController } override func viewDidLoad() { diff --git a/Kiosk/Bid Fulfillment/FulfillmentContainerViewController.swift b/Kiosk/Bid Fulfillment/FulfillmentContainerViewController.swift index 2569c2a5..91083d02 100644 --- a/Kiosk/Bid Fulfillment/FulfillmentContainerViewController.swift +++ b/Kiosk/Bid Fulfillment/FulfillmentContainerViewController.swift @@ -59,6 +59,6 @@ class FulfillmentContainerViewController: UIViewController { } class func instantiateFromStoryboard(_ storyboard: UIStoryboard) -> FulfillmentContainerViewController { - return storyboard.viewControllerWithID(.FulfillmentContainer) as! FulfillmentContainerViewController + return storyboard.viewController(withID: .FulfillmentContainer) as! FulfillmentContainerViewController } } diff --git a/Kiosk/Bid Fulfillment/KeypadViewModel.swift b/Kiosk/Bid Fulfillment/KeypadViewModel.swift index b9e8fb21..d2fbdb28 100644 --- a/Kiosk/Bid Fulfillment/KeypadViewModel.swift +++ b/Kiosk/Bid Fulfillment/KeypadViewModel.swift @@ -42,7 +42,7 @@ private extension KeypadViewModel { strongSelf.intValue.value = Int(strongSelf.intValue.value / 10) if strongSelf.stringValue.value.isNotEmpty { let string = strongSelf.stringValue.value - strongSelf.stringValue.value = string.substringToIndex(string.endIndex.predecessor()) + strongSelf.stringValue.value = string.substring(to: string.endIndex.predecessor()) } } observer.onCompleted() diff --git a/Kiosk/Bid Fulfillment/LoadingViewController.swift b/Kiosk/Bid Fulfillment/LoadingViewController.swift index 593e44c0..35c7845f 100644 --- a/Kiosk/Bid Fulfillment/LoadingViewController.swift +++ b/Kiosk/Bid Fulfillment/LoadingViewController.swift @@ -74,7 +74,7 @@ class LoadingViewController: UIViewController { }, onDisposed: { [weak self] in // Regardless of error or completion. hide the spinner. - self?.spinner.hidden = true + self?.spinner.isHidden = true }) .addDisposableTo(rx_disposeBag) } @@ -135,13 +135,13 @@ extension LoadingViewController { } let showPlaceHigherButton = placingBid && (!isHighestBidder || reserveNotMet) - placeHigherBidButton.hidden = !showPlaceHigherButton + placeHigherBidButton.isHidden = !showPlaceHigherButton let showAuctionButton = showPlaceHigherButton || isHighestBidder || (!placingBid && !createdNewBidder) - backToAuctionButton.hidden = !showAuctionButton + backToAuctionButton.isHidden = !showAuctionButton let title = reserveNotMet ? "NO, THANKS" : (createdNewBidder ? "CONTINUE" : "BACK TO AUCTION") - backToAuctionButton.setTitle(title, forState: .Normal) + backToAuctionButton.setTitle(title, for: .normal) fulfillmentContainer()?.cancelButton.isHidden = false } @@ -181,7 +181,7 @@ extension LoadingViewController { statusMessage.text = "You are the high bidder for this lot." bidConfirmationImageView.image = UIImage(named: "BidHighestBidder") - recognizer.rx_event.subscribeNext { [weak self] _ in + recognizer.rx.event.subscribeNext { [weak self] _ in self?.closeSelf() }.addDisposableTo(rx_disposeBag) @@ -208,22 +208,22 @@ extension LoadingViewController { if error.domain == OutbidDomain { handleLowestBidder() } else { - bidPlacementFailed(error) + bidPlacementFailed(error: error) } } else { // If you're not placing a bid, you're here because you're .just registering. - handleRegistrationFailed(error) + handleRegistrationFailed(error: error) } } func handleRegistrationFailed(error: NSError) { - handleErrorWithTitle("Registration Failed", + handleError(withTitle: "Registration Failed", message: "There was a problem registering for the auction. Please speak to an Artsy representative.", error: error) } func bidPlacementFailed(error: NSError) { - handleErrorWithTitle("Bid Failed", + handleError(withTitle: "Bid Failed", message: "There was a problem placing your bid. Please speak to an Artsy representative.", error: error) } diff --git a/Kiosk/Bid Fulfillment/ManualCreditCardInputViewController.swift b/Kiosk/Bid Fulfillment/ManualCreditCardInputViewController.swift index 9ab2cfec..eaf54df4 100644 --- a/Kiosk/Bid Fulfillment/ManualCreditCardInputViewController.swift +++ b/Kiosk/Bid Fulfillment/ManualCreditCardInputViewController.swift @@ -154,7 +154,7 @@ class ManualCreditCardInputViewController: UIViewController, RegistrationSubCont } class func instantiateFromStoryboard(_ storyboard: UIStoryboard) -> ManualCreditCardInputViewController { - return storyboard.viewControllerWithID(.ManualCardDetailsInput) as! ManualCreditCardInputViewController + return storyboard.viewController(withID: .ManualCardDetailsInput) as! ManualCreditCardInputViewController } } diff --git a/Kiosk/Bid Fulfillment/Models/BidDetails.swift b/Kiosk/Bid Fulfillment/Models/BidDetails.swift index cf5f743c..e753b180 100644 --- a/Kiosk/Bid Fulfillment/Models/BidDetails.swift +++ b/Kiosk/Bid Fulfillment/Models/BidDetails.swift @@ -53,8 +53,8 @@ import Moya .filterSuccessfulStatusCodes() .mapJSON() .flatMap { accessTokenDict -> Observable in - guard let accessToken = accessTokenDict["access_token"] as? String else { - return Observable.error(EidolonError.CouldNotParseJSON) + guard let accessToken = (accessTokenDict as AnyObject)["access_token"] as? String else { + return Observable.error(EidolonError.couldNotParseJSON) } return .just(Networking.newAuthorizedNetworking(accessToken)) diff --git a/Kiosk/Bid Fulfillment/Models/RegistrationCoordinator.swift b/Kiosk/Bid Fulfillment/Models/RegistrationCoordinator.swift index b1557161..cb5fbec8 100644 --- a/Kiosk/Bid Fulfillment/Models/RegistrationCoordinator.swift +++ b/Kiosk/Bid Fulfillment/Models/RegistrationCoordinator.swift @@ -43,26 +43,26 @@ class RegistrationCoordinator: NSObject { switch index { case .mobileVC: - return storyboard.viewControllerWithID(.RegisterMobile) + return storyboard.viewController(withID: .RegisterMobile) case .emailVC: - return storyboard.viewControllerWithID(.RegisterEmail) + return storyboard.viewController(withID: .RegisterEmail) case .passwordVC: - return storyboard.viewControllerWithID(.RegisterPassword) + return storyboard.viewController(withID: .RegisterPassword) case .zipCodeVC: - return storyboard.viewControllerWithID(.RegisterPostalorZip) + return storyboard.viewController(withID: .RegisterPostalorZip) case .creditCardVC: if AppSetup.sharedState.disableCardReader { - return storyboard.viewControllerWithID(.ManualCardDetailsInput) + return storyboard.viewController(withID: .ManualCardDetailsInput) } else { - return storyboard.viewControllerWithID(.RegisterCreditCard) + return storyboard.viewController(withID: .RegisterCreditCard) } case .confirmVC: - return storyboard.viewControllerWithID(.RegisterConfirm) + return storyboard.viewController(withID: .RegisterConfirm) } } diff --git a/Kiosk/Bid Fulfillment/PlaceBidViewController.swift b/Kiosk/Bid Fulfillment/PlaceBidViewController.swift index 13faad30..d5579db0 100644 --- a/Kiosk/Bid Fulfillment/PlaceBidViewController.swift +++ b/Kiosk/Bid Fulfillment/PlaceBidViewController.swift @@ -45,7 +45,7 @@ class PlaceBidViewController: UIViewController { var buyersPremium: () -> (BuyersPremium?) = { appDelegate().sale.buyersPremium } class func instantiateFromStoryboard(_ storyboard: UIStoryboard) -> PlaceBidViewController { - return storyboard.viewControllerWithID(.PlaceYourBid) as! PlaceBidViewController + return storyboard.viewController(withID: .PlaceYourBid) as! PlaceBidViewController } fileprivate let _viewWillDisappear = PublishSubject() @@ -192,7 +192,7 @@ class PlaceBidViewController: UIViewController { saleArtwork .artwork .rx_observe(NSAttributedString.self, "titleAndDate") - .takeUntil(rx_deallocating) + .takeUntil(rx.deallocated) .bindTo(artworkTitleLabel.rx_attributedText) .addDisposableTo(rx_disposeBag) @@ -200,7 +200,7 @@ class PlaceBidViewController: UIViewController { .artwork .rx_observe(String.self, "price") .filterNil() - .takeUntil(rx_deallocating) + .takeUntil(rx.deallocated) .bindTo(artworkPriceLabel.rx_text) .addDisposableTo(rx_disposeBag) diff --git a/Kiosk/Bid Fulfillment/RegisterViewController.swift b/Kiosk/Bid Fulfillment/RegisterViewController.swift index a04f6231..5fa5f281 100644 --- a/Kiosk/Bid Fulfillment/RegisterViewController.swift +++ b/Kiosk/Bid Fulfillment/RegisterViewController.swift @@ -34,7 +34,7 @@ class RegisterViewController: UIViewController { coordinator.storyboard = self.storyboard! let registerIndex = coordinator.currentIndex.asObservable() - let indexIsConfirmed = registerIndex.map { return ($0 == RegistrationIndex.ConfirmVC.toInt()) } + let indexIsConfirmed = registerIndex.map { return ($0 == RegistrationIndex.confirmVC.toInt()) } indexIsConfirmed .not() diff --git a/Kiosk/Bid Fulfillment/RegistrationEmailViewController.swift b/Kiosk/Bid Fulfillment/RegistrationEmailViewController.swift index 2fb20c9a..485397f1 100644 --- a/Kiosk/Bid Fulfillment/RegistrationEmailViewController.swift +++ b/Kiosk/Bid Fulfillment/RegistrationEmailViewController.swift @@ -8,7 +8,7 @@ class RegistrationEmailViewController: UIViewController, RegistrationSubControll var finished = PublishSubject() lazy var viewModel: GenericFormValidationViewModel = { - let emailIsValid = self.emailTextField.rx_text.map(stringIsEmailAddress) + let emailIsValid = self.emailTextField.rx.textInput.text.map(stringIsEmailAddress) return GenericFormValidationViewModel(isValid: emailIsValid, manualInvocation: self.emailTextField.rx_returnKey, finishedSubject: self.finished) }() @@ -24,7 +24,7 @@ class RegistrationEmailViewController: UIViewController, RegistrationSubControll super.viewDidLoad() emailTextField.text = bidDetails.newUser.email.value - emailTextField.rx_text + emailTextField.rx.textInput.text .asObservable() .mapToOptional() .takeUntil(viewWillDisappear) @@ -52,6 +52,6 @@ class RegistrationEmailViewController: UIViewController, RegistrationSubControll } class func instantiateFromStoryboard(_ storyboard: UIStoryboard) -> RegistrationEmailViewController { - return storyboard.viewControllerWithID(.RegisterEmail) as! RegistrationEmailViewController + return storyboard.viewController(withID: .RegisterEmail) as! RegistrationEmailViewController } } diff --git a/Kiosk/Bid Fulfillment/RegistrationMobileViewController.swift b/Kiosk/Bid Fulfillment/RegistrationMobileViewController.swift index ae170d03..6a44f62d 100644 --- a/Kiosk/Bid Fulfillment/RegistrationMobileViewController.swift +++ b/Kiosk/Bid Fulfillment/RegistrationMobileViewController.swift @@ -53,6 +53,6 @@ class RegistrationMobileViewController: UIViewController, RegistrationSubControl } class func instantiateFromStoryboard(_ storyboard: UIStoryboard) -> RegistrationMobileViewController { - return storyboard.viewControllerWithID(.RegisterMobile) as! RegistrationMobileViewController + return storyboard.viewController(withID: .RegisterMobile) as! RegistrationMobileViewController } } diff --git a/Kiosk/Bid Fulfillment/RegistrationPasswordViewController.swift b/Kiosk/Bid Fulfillment/RegistrationPasswordViewController.swift index 275b8352..eca0c9ed 100644 --- a/Kiosk/Bid Fulfillment/RegistrationPasswordViewController.swift +++ b/Kiosk/Bid Fulfillment/RegistrationPasswordViewController.swift @@ -120,6 +120,6 @@ class RegistrationPasswordViewController: UIViewController, RegistrationSubContr } class func instantiateFromStoryboard(_ storyboard: UIStoryboard) -> RegistrationPasswordViewController { - return storyboard.viewControllerWithID(.RegisterPassword) as! RegistrationPasswordViewController + return storyboard.viewController(withID: .RegisterPassword) as! RegistrationPasswordViewController } } diff --git a/Kiosk/Bid Fulfillment/RegistrationPostalZipViewController.swift b/Kiosk/Bid Fulfillment/RegistrationPostalZipViewController.swift index 2931de98..a6946cbb 100644 --- a/Kiosk/Bid Fulfillment/RegistrationPostalZipViewController.swift +++ b/Kiosk/Bid Fulfillment/RegistrationPostalZipViewController.swift @@ -42,6 +42,6 @@ class RegistrationPostalZipViewController: UIViewController, RegistrationSubCont } class func instantiateFromStoryboard(_ storyboard: UIStoryboard) -> RegistrationPostalZipViewController { - return storyboard.viewControllerWithID(.RegisterPostalorZip) as! RegistrationPostalZipViewController + return storyboard.viewController(withID: .RegisterPostalorZip) as! RegistrationPostalZipViewController } } diff --git a/Kiosk/Bid Fulfillment/SwipeCreditCardViewController.swift b/Kiosk/Bid Fulfillment/SwipeCreditCardViewController.swift index 5d8dad70..21f20ea8 100644 --- a/Kiosk/Bid Fulfillment/SwipeCreditCardViewController.swift +++ b/Kiosk/Bid Fulfillment/SwipeCreditCardViewController.swift @@ -16,7 +16,7 @@ class SwipeCreditCardViewController: UIViewController, RegistrationSubController @IBOutlet weak var titleLabel: ARSerifLabel! class func instantiateFromStoryboard(_ storyboard: UIStoryboard) -> SwipeCreditCardViewController { - return storyboard.viewControllerWithID(.RegisterCreditCard) as! SwipeCreditCardViewController + return storyboard.viewController(withID: .RegisterCreditCard) as! SwipeCreditCardViewController } let cardName = Variable("") @@ -128,7 +128,7 @@ private extension SwipeCreditCardViewController { func applyCardWithSuccess(_ success: Bool) { let cardFullDigits = success ? "4242424242424242" : "4000000000000002" - stripeManager.registerCard(cardFullDigits, month: 04, year: 2018, securityCode: "123", postalCode: "10013") + stripeManager.registerCard(digits: cardFullDigits, month: 04, year: 2018, securityCode: "123", postalCode: "10013") .subscribeNext() { [weak self] token in self?.cardName.value = "Kiosk Staging CC Test" diff --git a/Kiosk/Bid Fulfillment/YourBiddingDetailsViewController.swift b/Kiosk/Bid Fulfillment/YourBiddingDetailsViewController.swift index 4df0532f..d2f00b41 100644 --- a/Kiosk/Bid Fulfillment/YourBiddingDetailsViewController.swift +++ b/Kiosk/Bid Fulfillment/YourBiddingDetailsViewController.swift @@ -1,6 +1,7 @@ import UIKit import Artsy_UILabels import Artsy_UIButtons +import RxCocoa class YourBiddingDetailsViewController: UIViewController { @@ -28,20 +29,20 @@ class YourBiddingDetailsViewController: UIViewController { confirmationImageView.image = image } - bodyLabel?.makeSubstringsBold(["Bidder Number", "PIN"]) + bodyLabel?.makeSubstringsBold(text: ["Bidder Number", "PIN"]) bidDetails .paddleNumber .asObservable() .filterNil() - .bindTo(bidderNumberLabel.rx_text) + .bindTo(bidderNumberLabel.rx.text) .addDisposableTo(rx_disposeBag) bidDetails .bidderPIN .asObservable() .filterNil() - .bindTo(pinNumberLabel.rx_text) + .bindTo(pinNumberLabel.rx.text) .addDisposableTo(rx_disposeBag) } @@ -50,6 +51,6 @@ class YourBiddingDetailsViewController: UIViewController { } class func instantiateFromStoryboard(_ storyboard: UIStoryboard) -> YourBiddingDetailsViewController { - return storyboard.viewControllerWithID(.YourBidderDetails) as! YourBiddingDetailsViewController + return storyboard.viewController(withID: .YourBidderDetails) as! YourBiddingDetailsViewController } } diff --git a/Kiosk/Help/HelpViewController.swift b/Kiosk/Help/HelpViewController.swift index 393f8541..f3c60375 100644 --- a/Kiosk/Help/HelpViewController.swift +++ b/Kiosk/Help/HelpViewController.swift @@ -24,11 +24,11 @@ class HelpViewController: UIViewController { } var registerToBidCommand = { (enabled: Observable) -> CocoaAction in - appDelegate().registerToBidCommand(enabled) + appDelegate().registerToBidCommand(enabled: enabled) } var requestBidderDetailsCommand = { (enabled: Observable) -> CocoaAction in - appDelegate().requestBidderDetailsCommand(enabled) + appDelegate().requestBidderDetailsCommand(enabled: enabled) } var showPrivacyPolicyCommand = { () -> CocoaAction in @@ -95,7 +95,7 @@ private extension HelpViewController { let registerButton = blackButton(.registerButton, title: "Register") registerButton.rx_action = registerToBidCommand(connectedToInternetOrStubbing()) - let bidderDetailsLabel = titleLabel(.bidderDetailsLabel, title: "What Are Bidder Details?") + let bidderDetailsLabel = titleLabel(tag: .bidderDetailsLabel, title: "What Are Bidder Details?") let bidderDetailsExplainLabel = wrappingSerifLabel(.bidderDetailsExplainLabel, text: "The bidder number is how you can identify yourself to bid and see your place in bid history. The PIN is a four digit number that authenticates your bid.") bidderDetailsExplainLabel.makeSubstringsBold(["bidder number", "PIN"]) diff --git a/Kiosk/Observable+JSONAble.swift b/Kiosk/Observable+JSONAble.swift index feff5d61..e8b3e59e 100644 --- a/Kiosk/Observable+JSONAble.swift +++ b/Kiosk/Observable+JSONAble.swift @@ -3,12 +3,12 @@ import Moya import RxSwift enum EidolonError: String { - case CouldNotParseJSON - case NotLoggedIn - case MissingData + case couldNotParseJSON + case notLoggedIn + case missingData } -extension EidolonError: Error { } +extension EidolonError: Swift.Error { } extension Observable { @@ -18,7 +18,7 @@ extension Observable { func mapTo(object classType: B.Type) -> Observable { return self.map { json in guard let dict = json as? Dictionary else { - throw EidolonError.CouldNotParseJSON + throw EidolonError.couldNotParseJSON } return B.fromJSON(dict) @@ -29,11 +29,11 @@ extension Observable { func mapTo(arrayOf classType: B.Type) -> Observable<[B]> { return self.map { json in guard let array = json as? [AnyObject] else { - throw EidolonError.CouldNotParseJSON + throw EidolonError.couldNotParseJSON } guard let dicts = array as? [Dictionary] else { - throw EidolonError.CouldNotParseJSON + throw EidolonError.couldNotParseJSON } return dicts.map { B.fromJSON($0) } diff --git a/Kiosk/Observable+Operators.swift b/Kiosk/Observable+Operators.swift index 09eb9c33..153e4b70 100644 --- a/Kiosk/Observable+Operators.swift +++ b/Kiosk/Observable+Operators.swift @@ -108,8 +108,15 @@ extension Observable { } } +protocol BooleanType { + var boolValue: Bool { get } +} +extension Bool: BooleanType { + var boolValue: Bool { return self } +} + // Maps true to false and vice versa -extension Observable where Element: Bool { +extension Observable where Element: BooleanType { func not() -> Observable { return self.map { input in return !input.boolValue @@ -117,11 +124,11 @@ extension Observable where Element: Bool { } } -extension Collection where Iterator.Element: ObservableType, Iterator.Element.E: Bool { +extension Collection where Iterator.Element: ObservableType, Iterator.Element.E: BooleanType { func combineLatestAnd() -> Observable { return combineLatest { bools -> Bool in - bools.reduce(true, combine: { (memo, element) in + bools.reduce(true, { (memo, element) in return memo && element.boolValue }) } @@ -129,7 +136,7 @@ extension Collection where Iterator.Element: ObservableType, Iterator.Element.E: func combineLatestOr() -> Observable { return combineLatest { bools in - bools.reduce(false, combine: { (memo, element) in + bools.reduce(false, { (memo, element) in return memo || element.boolValue }) } diff --git a/Kiosk/Sale Artwork Details/SaleArtworkDetailsViewController.swift b/Kiosk/Sale Artwork Details/SaleArtworkDetailsViewController.swift index 6b4ed16a..83ccb554 100644 --- a/Kiosk/Sale Artwork Details/SaleArtworkDetailsViewController.swift +++ b/Kiosk/Sale Artwork Details/SaleArtworkDetailsViewController.swift @@ -18,11 +18,11 @@ class SaleArtworkDetailsViewController: UIViewController { } class func instantiateFromStoryboard(_ storyboard: UIStoryboard) -> SaleArtworkDetailsViewController { - return storyboard.viewControllerWithID(.SaleArtworkDetail) as! SaleArtworkDetailsViewController + return storyboard.viewController(withID: .SaleArtworkDetail) as! SaleArtworkDetailsViewController } - lazy var artistInfo: Observable = { - let artistInfo = self.provider.request(.Artwork(id: self.saleArtwork.artwork.id)).filterSuccessfulStatusCodes().mapJSON() + lazy var artistInfo: Observable = { + let artistInfo = self.provider.request(.artwork(id: self.saleArtwork.artwork.id)).filterSuccessfulStatusCodes().mapJSON() return artistInfo.shareReplay(1) }() @@ -130,7 +130,7 @@ class SaleArtworkDetailsViewController: UIViewController { .viewModel .lotNumber() .filterNil() - .bindTo(lotNumberLabel.rx_text) + .bindTo(lotNumberLabel.rx.text) .addDisposableTo(rx_disposeBag) } @@ -355,7 +355,7 @@ class SaleArtworkDetailsViewController: UIViewController { .take(1) .subscribeNext { [weak label] (_) in if let label = label { - label.preferredMaxLayoutWidth = CGRectGetWidth(label.frame) + label.preferredMaxLayoutWidth = label.frame.width } } .addDisposableTo(rx_disposeBag) diff --git a/Kiosk/Supporting Files/PodsBridgingHeader.h b/Kiosk/Supporting Files/PodsBridgingHeader.h index a0f6421e..fe750df6 100644 --- a/Kiosk/Supporting Files/PodsBridgingHeader.h +++ b/Kiosk/Supporting Files/PodsBridgingHeader.h @@ -13,10 +13,6 @@ #import #import -//#import -//#import -//#import -#import #import // Fonts can come from one of two Pods diff --git a/Kiosk/UIKit+Rx.swift b/Kiosk/UIKit+Rx.swift index aa959868..0e9e11ec 100644 --- a/Kiosk/UIKit+Rx.swift +++ b/Kiosk/UIKit+Rx.swift @@ -22,6 +22,6 @@ extension UIView { extension UITextField { var rx_returnKey: Observable { - return rx_controlEvent(.EditingDidEndOnExit).takeUntil(rx_deallocating) + return rx_controlEvent(.EditingDidEndOnExit).takeUntil(rx.deallocated) } } diff --git a/KioskTests/ListingsViewControllerTests.swift b/KioskTests/ListingsViewControllerTests.swift index e382e9a8..0a95b084 100644 --- a/KioskTests/ListingsViewControllerTests.swift +++ b/KioskTests/ListingsViewControllerTests.swift @@ -128,7 +128,7 @@ class ListingsViewControllerTestsStubbedViewModel: NSObject, ListingsViewModelTy return Observable.just(NSDate()) } - var scheduleOnBackground: (_ observable: Observable) -> Observable = { observable in observable } + var scheduleOnBackground: (_ observable: Observable) -> Observable = { observable in observable } var scheduleOnForeground: (_ observable: Observable<[SaleArtwork]>) -> Observable<[SaleArtwork]> = { observable in observable } func saleArtworkViewModelAtIndexPath(_ indexPath: NSIndexPath) -> SaleArtworkViewModel { diff --git a/KioskTests/ListingsViewModelTests.swift b/KioskTests/ListingsViewModelTests.swift index be1e3c6c..73066aea 100644 --- a/KioskTests/ListingsViewModelTests.swift +++ b/KioskTests/ListingsViewModelTests.swift @@ -5,7 +5,7 @@ import Moya @testable import Kiosk -let testScheduleOnBackground: (_ observable: Observable) -> Observable = { observable in observable } +let testScheduleOnBackground: (_ observable: Observable) -> Observable = { observable in observable } let testScheduleOnForeground: (_ observable: Observable<[SaleArtwork]>) -> Observable<[SaleArtwork]> = { observable in observable } class ListingsViewModelTests: QuickSpec { diff --git a/Podfile b/Podfile index cb58580e..3a38511b 100644 --- a/Podfile +++ b/Podfile @@ -36,9 +36,8 @@ else pod 'Artsy+OSSUIFonts', '~> 1.1.0' end -pod 'ORStackView' -pod 'FLKAutoLayout' -pod 'ISO8601DateFormatter', '0.7' +pod 'ORStackView', '2.0' +pod 'FLKAutoLayout', '0.1.1' pod 'ARCollectionViewMasonryLayout', '~> 2.0.0' pod 'SDWebImage', '~> 3.7' pod 'SVProgressHUD' diff --git a/Podfile.lock b/Podfile.lock index 6d19606e..06a3eca8 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -39,7 +39,6 @@ PODS: - Forgeries/Core (= 1.0.0) - Forgeries/Core (1.0.0) - HockeySDK-Source (3.8.1) - - ISO8601DateFormatter (0.7) - Keys (1.0.0) - Mixpanel (2.8.3): - Mixpanel/Mixpanel (= 2.8.3) @@ -62,7 +61,7 @@ PODS: - NJKWebViewProgress/ProgressView (0.2.3) - NSObject+Rx (2.0.0): - RxSwift - - ORStackView (3.0.0): + - ORStackView (2.0.0): - FLKAutoLayout (~> 0.1) - Quick (0.10.0) - Reachability (3.1.1) @@ -99,15 +98,14 @@ DEPENDENCIES: - DZNWebViewController (from `https://github.com/orta/DZNWebViewController.git`) - ECPhoneNumberFormatter - FBSnapshotTestCase (from `https://github.com/facebook/ios-snapshot-test-case`, commit `1639f694a2100cdeadf2f8fa14225cdd3759e75b`) - - FLKAutoLayout + - FLKAutoLayout (= 0.1.1) - Forgeries - - ISO8601DateFormatter (= 0.7) - Keys (from `Pods/CocoaPodsKeys`) - Moya/RxSwift (= 8.0.0-beta.2) - Nimble - Nimble-Snapshots - NSObject+Rx - - ORStackView + - ORStackView (= 2.0) - Quick - Reachability (from `https://github.com/ashfurrow/Reachability.git`, branch `frameworks`) - RxBlocking (= 3.0.0-beta.2) @@ -168,7 +166,6 @@ SPEC CHECKSUMS: fmemopen: dc31f7d3004644b33deee95e047305d23f8cfdd4 Forgeries: 64ced144ea8341d89a7eec9d1d7986f0f1366250 HockeySDK-Source: d03f7d267d78980af33fb63ddea5199f5bf84049 - ISO8601DateFormatter: ab926648eebe497f4d167c0fd083992f959f1274 Keys: 9c35bf00f612ee1d48556f4a4b9b4551e224e90f Mixpanel: fd52e097ae0d27295d0595b2261c60b94d191fda Moya: 920d40f738e6f4ece84010cec701bb9693be8251 @@ -176,7 +173,7 @@ SPEC CHECKSUMS: Nimble-Snapshots: 2da4d2ec829b458d6886c06812c52493fce284c9 NJKWebViewProgress: f481fd424cb5ecc27eacae11b5397db92fba9a4d NSObject+Rx: 2a9cd801d9c847e6d2486cbad8d7701b67834e70 - ORStackView: 5836e207304ed62db4177b707f19245454943705 + ORStackView: 5df6b1b990b0648d8ef6f69f89361ea8648e8f64 Quick: 5d290df1c69d5ee2f0729956dcf0fd9a30447eaa Reachability: 5b69def55ffa04d6f9425a6da1f47338b60557b2 Result: 1b3e431f37cbcd3ad89c6aa9ab0ae55515fae3b6 @@ -191,6 +188,6 @@ SPEC CHECKSUMS: UIView+BooleanAnimations: a760be9a066036e55f298b7b7350a6cb14cfcd97 XNGMarkdownParser: 562999edfa0e0913dc345f0931bdd78be59f826e -PODFILE CHECKSUM: da7de32d81a4e283f5f6cc3e3e9761774eef0dd1 +PODFILE CHECKSUM: 17758583653f79ede3ea0297c1bd6da5d077cf51 COCOAPODS: 1.1.0.rc.2 From ebc39282e8dcdf855160c3329b6c9189dc156649 Mon Sep 17 00:00:00 2001 From: Ash Furrow Date: Tue, 4 Oct 2016 19:48:01 -0400 Subject: [PATCH 05/46] [Swift 3] Updates Podfile to CocoaPods 1.0. --- Kiosk.xcodeproj/project.pbxproj | 60 +++++++++++++++++++++++ Podfile | 84 +++++++++++++++++---------------- Podfile.lock | 43 +++++++++-------- 3 files changed, 125 insertions(+), 62 deletions(-) diff --git a/Kiosk.xcodeproj/project.pbxproj b/Kiosk.xcodeproj/project.pbxproj index d0efcd3c..b7990353 100644 --- a/Kiosk.xcodeproj/project.pbxproj +++ b/Kiosk.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 1AEA9736F27FF8E8CD5C6F0F /* Pods_Kiosk.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E087BDDF167E52F60049D0C4 /* Pods_Kiosk.framework */; }; 5E0A4E3E1B41C9C800CB2BEA /* UIView+LongPressDisplayMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E0A4E3D1B41C9C800CB2BEA /* UIView+LongPressDisplayMessage.swift */; }; 5E19E6501A6564B300F8CBDD /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E19E64E1A65630200F8CBDD /* Logger.swift */; }; 5E19E6521A656CCF00F8CBDD /* LoggerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E19E6511A656CCF00F8CBDD /* LoggerTests.swift */; }; @@ -236,6 +237,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 105ED099C82E240D7547D3A6 /* Pods-Kiosk.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Kiosk.release.xcconfig"; path = "Pods/Target Support Files/Pods-Kiosk/Pods-Kiosk.release.xcconfig"; sourceTree = ""; }; 276814418ACEF21BC773CB08 /* Pods-KioskTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KioskTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-KioskTests/Pods-KioskTests.debug.xcconfig"; sourceTree = ""; }; 5E0A4E3D1B41C9C800CB2BEA /* UIView+LongPressDisplayMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UIView+LongPressDisplayMessage.swift"; path = "Kiosk/UIView+LongPressDisplayMessage.swift"; sourceTree = SOURCE_ROOT; }; 5E19E64E1A65630200F8CBDD /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; @@ -440,7 +442,9 @@ 5EFFE24F1B978C3C006BF64C /* ListingsViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListingsViewModelTests.swift; sourceTree = ""; }; 620BB28AC1DE3936837D2731 /* Pods-KioskTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KioskTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-KioskTests/Pods-KioskTests.release.xcconfig"; sourceTree = ""; }; 94D0A1FE57C6B14D52392DF4 /* Pods_KioskTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_KioskTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9D18FBF2740DA862421FE723 /* Pods-Kiosk.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Kiosk.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Kiosk/Pods-Kiosk.debug.xcconfig"; sourceTree = ""; }; B5DFB9BD12B5173B74D620D7 /* Pods.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + E087BDDF167E52F60049D0C4 /* Pods_Kiosk.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Kiosk.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E70556721BC40BFB0064ABF8 /* MyBidPosition.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = MyBidPosition.json; sourceTree = ""; }; /* End PBXFileReference section */ @@ -449,6 +453,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 1AEA9736F27FF8E8CD5C6F0F /* Pods_Kiosk.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -878,6 +883,7 @@ children = ( B5DFB9BD12B5173B74D620D7 /* Pods.framework */, 94D0A1FE57C6B14D52392DF4 /* Pods_KioskTests.framework */, + E087BDDF167E52F60049D0C4 /* Pods_Kiosk.framework */, ); name = Frameworks; sourceTree = ""; @@ -887,6 +893,8 @@ children = ( 276814418ACEF21BC773CB08 /* Pods-KioskTests.debug.xcconfig */, 620BB28AC1DE3936837D2731 /* Pods-KioskTests.release.xcconfig */, + 9D18FBF2740DA862421FE723 /* Pods-Kiosk.debug.xcconfig */, + 105ED099C82E240D7547D3A6 /* Pods-Kiosk.release.xcconfig */, ); name = Pods; sourceTree = ""; @@ -898,9 +906,12 @@ isa = PBXNativeTarget; buildConfigurationList = 5EB0B70C1A5C3E6800B7CBF2 /* Build configuration list for PBXNativeTarget "Kiosk" */; buildPhases = ( + 0B78B80EF75AFBA025C46BD5 /* [CP] Check Pods Manifest.lock */, 5EB0B6E91A5C3E6800B7CBF2 /* Sources */, 5EB0B6EA1A5C3E6800B7CBF2 /* Frameworks */, 5EB0B6EB1A5C3E6800B7CBF2 /* Resources */, + 34A5B24DE77329AC1D3FCEE3 /* [CP] Embed Pods Frameworks */, + DFF2EE1CB701147444967391 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -1046,6 +1057,21 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 0B78B80EF75AFBA025C46BD5 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; 0F1DDDE620287465CADB7C17 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -1076,6 +1102,21 @@ shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; + 34A5B24DE77329AC1D3FCEE3 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Kiosk/Pods-Kiosk-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 4D2169D120D205285B5A661F /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -1091,6 +1132,21 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-KioskTests/Pods-KioskTests-resources.sh\"\n"; showEnvVarsInLog = 0; }; + DFF2EE1CB701147444967391 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Kiosk/Pods-Kiosk-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -1284,6 +1340,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; @@ -1328,6 +1385,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; @@ -1362,6 +1420,7 @@ }; 5EB0B70D1A5C3E6800B7CBF2 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 9D18FBF2740DA862421FE723 /* Pods-Kiosk.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; @@ -1387,6 +1446,7 @@ }; 5EB0B70E1A5C3E6800B7CBF2 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 105ED099C82E240D7547D3A6 /* Pods-Kiosk.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; diff --git a/Podfile b/Podfile index 3a38511b..4d08dc2e 100644 --- a/Podfile +++ b/Podfile @@ -25,55 +25,59 @@ use_frameworks! # Yep. inhibit_all_warnings! -# Artsy stuff -pod 'Artsy+UIColors' -pod 'Artsy+UILabels' -pod 'Artsy-UIButtons' +target 'Kiosk' do -if ['orta', 'ash', 'artsy', 'Laura', 'alan', 'CI', 'distiller', 'travis'].include?(ENV['USER']) - pod 'Artsy+UIFonts', '~> 1.1.0' -else - pod 'Artsy+OSSUIFonts', '~> 1.1.0' -end + # Artsy stuff + pod 'Artsy+UIColors' + pod 'Artsy+UILabels' + pod 'Artsy-UIButtons' + + if ['orta', 'ash', 'artsy', 'Laura', 'alan', 'CI', 'distiller', 'travis'].include?(ENV['USER']) + pod 'Artsy+UIFonts', '~> 1.1.0' + else + pod 'Artsy+OSSUIFonts', '~> 1.1.0' + end -pod 'ORStackView', '2.0' -pod 'FLKAutoLayout', '0.1.1' -pod 'ARCollectionViewMasonryLayout', '~> 2.0.0' -pod 'SDWebImage', '~> 3.7' -pod 'SVProgressHUD' + pod 'ORStackView', '2.0' + pod 'FLKAutoLayout', '0.1.1' + pod 'ARCollectionViewMasonryLayout', '~> 2.0.0' + pod 'SDWebImage', '~> 3.7' + pod 'SVProgressHUD' -pod 'ARAnalytics/Mixpanel' -pod 'ARAnalytics/HockeyApp' + pod 'ARAnalytics/Mixpanel' + pod 'ARAnalytics/HockeyApp' -pod 'CardFlight' -pod 'Stripe' -pod 'ECPhoneNumberFormatter' -pod 'UIImageViewAligned', :git => 'https://github.com/ashfurrow/UIImageViewAligned.git' -pod 'DZNWebViewController', :git => 'https://github.com/orta/DZNWebViewController.git' -pod 'Reachability', :git => 'https://github.com/ashfurrow/Reachability.git', :branch => 'frameworks' + pod 'CardFlight' + pod 'Stripe' + pod 'ECPhoneNumberFormatter' + pod 'UIImageViewAligned', :git => 'https://github.com/ashfurrow/UIImageViewAligned.git' + pod 'DZNWebViewController', :git => 'https://github.com/orta/DZNWebViewController.git' + pod 'Reachability', :git => 'https://github.com/ashfurrow/Reachability.git', :branch => 'frameworks' -pod 'UIView+BooleanAnimations' -pod 'ARTiledImageView' -pod 'XNGMarkdownParser' + pod 'UIView+BooleanAnimations' + pod 'ARTiledImageView' + pod 'XNGMarkdownParser' -# Swift pods -pod 'SwiftyJSON' -pod 'RxSwift', '3.0.0-beta.2' -pod 'RxCocoa', '3.0.0-beta.2' -pod 'Moya/RxSwift', '8.0.0-beta.2' -pod 'NSObject+Rx' -pod 'Action', '2.0.0-beta.1' + # Swift pods + pod 'SwiftyJSON' + pod 'RxSwift', '3.0.0-beta.2' + pod 'RxCocoa', '3.0.0-beta.2' + pod 'Moya/RxSwift', '8.0.0-beta.2' + pod 'NSObject+Rx' + pod 'Action', '2.0.0-beta.1' -target 'KioskTests' do + target 'KioskTests' do + inherit! :search_paths - # To get around this issue: https://github.com/facebook/ios-snapshot-test-case/issues/167 - pod 'FBSnapshotTestCase', :git => 'https://github.com/facebook/ios-snapshot-test-case', :commit => '1639f694a2100cdeadf2f8fa14225cdd3759e75b' - pod 'Nimble-Snapshots' - pod 'Quick' - pod 'Nimble' - pod 'Forgeries' - pod 'RxBlocking', '3.0.0-beta.2' + # To get around this issue: https://github.com/facebook/ios-snapshot-test-case/issues/167 + pod 'FBSnapshotTestCase', :git => 'https://github.com/facebook/ios-snapshot-test-case', :commit => '1639f694a2100cdeadf2f8fa14225cdd3759e75b' + pod 'Nimble-Snapshots' + pod 'Quick' + pod 'Nimble' + pod 'Forgeries' + pod 'RxBlocking', '3.0.0-beta.2' + end end post_install do |installer| diff --git a/Podfile.lock b/Podfile.lock index 06a3eca8..fda1d0fc 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -3,23 +3,24 @@ PODS: - RxCocoa - RxSwift - Alamofire (4.0.1) - - ARAnalytics/CoreIOS (3.8.0) - - ARAnalytics/HockeyApp (3.8.0): + - ARAnalytics/CoreIOS (4.0.0) + - ARAnalytics/HockeyApp (4.0.0): - ARAnalytics/CoreIOS - HockeySDK-Source - - ARAnalytics/Mixpanel (3.8.0): + - ARAnalytics/Mixpanel (4.0.0): - ARAnalytics/CoreIOS - Mixpanel - ARCollectionViewMasonryLayout (2.0.0) - ARTiledImageView (1.1.1): - SDWebImage - - Artsy+UIColors (1.0.0): - - EDColor (~> 0.4) + - Artsy+UIColors (3.0.1) - Artsy+UIFonts (1.1.0) - - Artsy+UILabels (1.3.1): - - Artsy+UIColors - - Artsy-UIButtons (1.4.0): - - Artsy+UIColors + - Artsy+UILabels (2.0.2): + - Artsy+UIColors (~> 3.0) + - Artsy+UIFonts + - Artsy-UIButtons (2.0.1): + - Artsy+UIColors (~> 3.0) + - Artsy+UIFonts - UIView+BooleanAnimations - CardFlight (3.0): - CardFlight/AudioJack (= 3.0) @@ -27,7 +28,6 @@ PODS: - DZNWebViewController (2.0): - NJKWebViewProgress (~> 0.2) - ECPhoneNumberFormatter (0.1.1) - - EDColor (0.4.0) - FBSnapshotTestCase (2.1.0): - FBSnapshotTestCase/SwiftSupport (= 2.1.0) - FBSnapshotTestCase/Core (2.1.0) @@ -38,11 +38,11 @@ PODS: - Forgeries (1.0.0): - Forgeries/Core (= 1.0.0) - Forgeries/Core (1.0.0) - - HockeySDK-Source (3.8.1) + - HockeySDK-Source (4.1.1) - Keys (1.0.0) - - Mixpanel (2.8.3): - - Mixpanel/Mixpanel (= 2.8.3) - - Mixpanel/Mixpanel (2.8.3) + - Mixpanel (3.0.4): + - Mixpanel/Mixpanel (= 3.0.4) + - Mixpanel/Mixpanel (3.0.4) - Moya/Core (8.0.0-beta.2): - Alamofire (~> 4.0.0) - Result @@ -150,24 +150,23 @@ CHECKOUT OPTIONS: SPEC CHECKSUMS: Action: 13a8e52481834751caf428bcf6c527e3db2aefc7 Alamofire: 7682d43245de14874acd142ec137b144aa1dd335 - ARAnalytics: 5468652928cee7100dd0e6b5162631409da37106 + ARAnalytics: d8a0eee8da481d146ca8af5a9ac632b5c4d17a81 ARCollectionViewMasonryLayout: 164b82010cf8ec99bc7a38cffe59a179d7e5a116 ARTiledImageView: 8c6fd11d9e8459a853d94394adea3a5e8c9329ba - Artsy+UIColors: 7a4c987885a8d8da3e22672a3232761f929dd2b6 + Artsy+UIColors: 10a2d71e9ffe1015be43d4e084d13ff4337aab97 Artsy+UIFonts: c51bb3b5cbf9c1a5fe198b4385d49133c5c71f5a - Artsy+UILabels: 43da757ee01bce8165ec83123eccccdf1fe8843c - Artsy-UIButtons: 6f67de2a5285f18acb5d0e2919d1b17188acbc27 + Artsy+UILabels: 9b8e8b683488e22633625db9627cd79ab64b610f + Artsy-UIButtons: 26e6d1e0d2174773e9cf7693985d27faddd61743 CardFlight: c27f6b57053ff7ee1918fd8130af289eef97195e DZNWebViewController: 54e8ee1c5bcc43e341ad77737631098d0d76db69 ECPhoneNumberFormatter: 061041e7715f8d3f2e8e2a069ddf28c52f7bd314 - EDColor: 89d19a013279a5604d61650721c15f20fefaaf00 FBSnapshotTestCase: 366ecd378511d7716c79991cd8067d1eed23578d FLKAutoLayout: 95b12c5cd9652100235140b68652f063f7437851 fmemopen: dc31f7d3004644b33deee95e047305d23f8cfdd4 Forgeries: 64ced144ea8341d89a7eec9d1d7986f0f1366250 - HockeySDK-Source: d03f7d267d78980af33fb63ddea5199f5bf84049 + HockeySDK-Source: a86f75afee838fc50826eb7a17875dea66844a37 Keys: 9c35bf00f612ee1d48556f4a4b9b4551e224e90f - Mixpanel: fd52e097ae0d27295d0595b2261c60b94d191fda + Mixpanel: 4a2330c26556109d2f42b01e36a05eb9e2caf25e Moya: 920d40f738e6f4ece84010cec701bb9693be8251 Nimble: 56fc9f5020effa2206de22c3dd910f4fb011b92f Nimble-Snapshots: 2da4d2ec829b458d6886c06812c52493fce284c9 @@ -188,6 +187,6 @@ SPEC CHECKSUMS: UIView+BooleanAnimations: a760be9a066036e55f298b7b7350a6cb14cfcd97 XNGMarkdownParser: 562999edfa0e0913dc345f0931bdd78be59f826e -PODFILE CHECKSUM: 17758583653f79ede3ea0297c1bd6da5d077cf51 +PODFILE CHECKSUM: baab7f8e9be67d7b89c9de732d1b94fd83a28c1b COCOAPODS: 1.1.0.rc.2 From ac9504c84c3a87a9c73ee76294a3a03cbd3f7a9f Mon Sep 17 00:00:00 2001 From: Ash Furrow Date: Fri, 7 Oct 2016 12:06:44 -0400 Subject: [PATCH 06/46] [Swift 3] Fixes more errors. --- .../AdminCardTestingViewController.swift | 6 +- Kiosk/Admin/AuctionWebViewController.swift | 2 +- Kiosk/Admin/ChooseAuctionViewController.swift | 10 +-- Kiosk/App/AppDelegate+GlobalActions.swift | 60 +++++++++--------- Kiosk/App/AppViewController.swift | 4 +- Kiosk/App/BidderDetailsRetrieval.swift | 22 +++---- Kiosk/App/Models/Artwork.swift | 8 +-- Kiosk/App/Models/BidderPosition.swift | 3 +- Kiosk/App/Models/Sale.swift | 5 +- Kiosk/App/Models/SaleArtwork.swift | 10 +-- Kiosk/App/Models/SaleArtworkViewModel.swift | 26 ++++---- Kiosk/App/NSErrorExtensions.swift | 6 +- .../App/UIViewSubclassesErrorExtensions.swift | 12 ++-- .../App/Views/Button Subclasses/Button.swift | 20 +++--- Kiosk/App/Views/RegisterFlowView.swift | 8 +-- Kiosk/App/Views/Spinner.swift | 4 +- Kiosk/App/Views/SwitchView.swift | 6 +- Kiosk/App/Views/Text Fields/TextField.swift | 8 +-- .../ListingsCountdownManager.swift | 5 +- .../Auction Listings/ListingsViewModel.swift | 2 +- .../MasonryCollectionViewCell.swift | 10 +-- .../TableCollectionViewCell.swift | 4 +- .../Auction Listings/WebViewController.swift | 2 +- .../AdminCCBypassNetworkModel.swift | 12 ++-- .../BidCheckingNetworkModel.swift | 18 +++--- ...nfirmYourBidArtsyLoginViewController.swift | 24 +++---- .../ConfirmYourBidPINViewController.swift | 24 +++---- .../ConfirmYourBidViewController.swift | 6 +- .../FulfillmentNavigationController.swift | 4 +- .../GenericFormValidationViewModel.swift | 4 +- Kiosk/Bid Fulfillment/KeypadViewModel.swift | 8 +-- .../LoadingViewController.swift | 5 +- Kiosk/Bid Fulfillment/LoadingViewModel.swift | 2 +- .../ManualCreditCardInputViewController.swift | 14 ++--- .../ManualCreditCardInputViewModel.swift | 16 ++--- .../PlaceBidNetworkModel.swift | 6 +- .../PlaceBidViewController.swift | 35 ++++++----- .../RegistrationMobileViewController.swift | 4 +- .../RegistrationPasswordViewController.swift | 14 ++--- .../RegistrationPostalZipViewController.swift | 4 +- Kiosk/Bid Fulfillment/StripeManager.swift | 10 +-- .../SwipeCreditCardViewController.swift | 2 +- .../YourBiddingDetailsViewController.swift | 5 +- Kiosk/Help/HelpAnimator.swift | 8 +-- Kiosk/Help/HelpViewController.swift | 32 +++++----- Kiosk/ListingsCollectionViewCell.swift | 46 +++++++------- Kiosk/Observable+Logging.swift | 6 +- Kiosk/Observable+Operators.swift | 6 ++ .../SaleArtworkDetailsViewController.swift | 63 ++++++++++--------- Kiosk/Supporting Files/PodsBridgingHeader.h | 3 +- Kiosk/UIKit+Rx.swift | 10 +-- Kiosk/UILabel+Fonts.swift | 10 +-- Kiosk/UIView+LongPressDisplayMessage.swift | 4 +- 53 files changed, 334 insertions(+), 314 deletions(-) diff --git a/Kiosk/Admin/AdminCardTestingViewController.swift b/Kiosk/Admin/AdminCardTestingViewController.swift index 79137033..25cfb2b9 100644 --- a/Kiosk/Admin/AdminCardTestingViewController.swift +++ b/Kiosk/Admin/AdminCardTestingViewController.swift @@ -24,14 +24,14 @@ class AdminCardTestingViewController: UIViewController { cardHandler.cardStatus .subscribe { (event) in switch event { - case .Next(let message): + case .next(let message): self.log("\(message)") - case .Error(let error): + case .error(let error): self.log("\n====Error====\n\(error)\nThe card reader may have become disconnected.\n\n") if self.cardHandler.card != nil { self.log("==\n\(self.cardHandler.card!)\n\n") } - case .Completed: + case .completed: guard let card = self.cardHandler.card else { // Restarts the card reader self.cardHandler.startSearching() diff --git a/Kiosk/Admin/AuctionWebViewController.swift b/Kiosk/Admin/AuctionWebViewController.swift index d33bd348..fb6cf0cb 100644 --- a/Kiosk/Admin/AuctionWebViewController.swift +++ b/Kiosk/Admin/AuctionWebViewController.swift @@ -15,7 +15,7 @@ class AuctionWebViewController: WebViewController { func exit() { let passwordVC = PasswordAlertViewController.alertView { [weak self] in - self?.navigationController?.popViewController(animated: true) + _ = self?.navigationController?.popViewController(animated: true) return } self.present(passwordVC, animated: true) {} diff --git a/Kiosk/Admin/ChooseAuctionViewController.swift b/Kiosk/Admin/ChooseAuctionViewController.swift index b537fb3f..e2a38339 100644 --- a/Kiosk/Admin/ChooseAuctionViewController.swift +++ b/Kiosk/Admin/ChooseAuctionViewController.swift @@ -29,12 +29,12 @@ class ChooseAuctionViewController: UIViewController { let title = " \(sale.name) - #\(sale.auctionState) - \(sale.artworkCount)" let button = ARFlatButton() - button.setTitle(title, forState: .normal) - button.setTitleColor(.blackColor(), forState: .normal) + button.setTitle(title, for: .normal) + button.setTitleColor(.black, for: .normal) button.tag = i button.rx.tap.subscribeNext { (_) in - let defaults = NSUserDefaults.standardUserDefaults() - defaults.setObject(sale.id, forKey: "KioskAuctionID") + let defaults = UserDefaults.standard + defaults.set(sale.id, forKey: "KioskAuctionID") defaults.synchronize() exit(1) } @@ -50,6 +50,6 @@ class ChooseAuctionViewController: UIViewController { @IBOutlet weak var stackScrollView: ORStackView! @IBAction func backButtonTapped(_ sender: AnyObject) { - self.navigationController?.popViewController(animated: true) + _ = self.navigationController?.popViewController(animated: true) } } diff --git a/Kiosk/App/AppDelegate+GlobalActions.swift b/Kiosk/App/AppDelegate+GlobalActions.swift index f348a1b4..5793caf3 100644 --- a/Kiosk/App/AppDelegate+GlobalActions.swift +++ b/Kiosk/App/AppDelegate+GlobalActions.swift @@ -17,7 +17,7 @@ extension AppDelegate { } internal var appViewController: AppViewController! { - let nav = self.window?.rootViewController?.findChildViewControllerOfType(UINavigationController) as? UINavigationController + let nav = self.window?.rootViewController?.findChildViewControllerOfType(UINavigationController.self) as? UINavigationController return nav?.delegate as? AppViewController } @@ -32,17 +32,17 @@ extension AppDelegate { window?.layoutIfNeeded() helpIsVisisble.subscribeNext { visisble in - let image: UIImage? = visisble ? UIImage(named: "xbtn_white")?.imageWithRenderingMode(.AlwaysOriginal) : nil + let image: UIImage? = visisble ? UIImage(named: "xbtn_white")?.withRenderingMode(.alwaysOriginal) : nil let text: String? = visisble ? nil : "HELP" - self.helpButton.setTitle(text, forState: .Normal) - self.helpButton.setImage(image, forState: .Normal) + self.helpButton.setTitle(text, for: .normal) + self.helpButton.setImage(image, for: .normal) let transition = CATransition() transition.duration = AnimationDuration.Normal transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) transition.type = kCATransitionFade - self.helpButton.layer.addAnimation(transition, forKey: "fade") + self.helpButton.layer.add(transition, forKey: "fade") }.addDisposableTo(rx_disposeBag) } @@ -60,7 +60,7 @@ extension AppDelegate { func showBuyersPremiumCommand(enabled: Observable = .just(true)) -> CocoaAction { return CocoaAction(enabledIf: enabled) { _ in self.hideAllTheThings() - .then(self.showWebController("https://m.artsy.net/auction/\(self.sale.id)/buyers-premium")) + .then(self.showWebController(address: "https://m.artsy.net/auction/\(self.sale.id)/buyers-premium")) .map(void) } } @@ -95,13 +95,13 @@ extension AppDelegate { func showPrivacyPolicyCommand() -> CocoaAction { return CocoaAction { _ in - self.hideAllTheThings().then(self.showWebController("https://artsy.net/privacy")) + self.hideAllTheThings().then(self.showWebController(address: "https://artsy.net/privacy")) } } func showConditionsOfSaleCommand() -> CocoaAction { return CocoaAction { _ in - self.hideAllTheThings().then(self.showWebController("https://artsy.net/conditions-of-sale")) + self.hideAllTheThings().then(self.showWebController(address: "https://artsy.net/conditions-of-sale")) } } } @@ -123,7 +123,7 @@ private extension AppDelegate { func showBidderDetailsRetrieval() -> Observable { let appVC = self.appViewController let presentingViewController: UIViewController = (appVC!.presentedViewController ?? appVC!) - return presentingViewController.promptForBidderDetailsRetrieval(self.provider) + return presentingViewController.promptForBidderDetailsRetrieval(provider: self.provider) } func showRegistration() -> Observable { @@ -143,38 +143,38 @@ private extension AppDelegate { internalNav.viewControllers = [registerVC] } - self.appViewController.presentViewController(containerController, animated: false) { + self.appViewController.present(containerController, animated: false) { containerController.viewDidAppearAnimation(containerController.allowAnimations) - sendDispatchCompleted(observer) + sendDispatchCompleted(to: observer) } - return NopDisposable.instance + return Disposables.create() } } func showHelp() -> Observable { return Observable.create { observer in let helpViewController = HelpViewController() - helpViewController.modalPresentationStyle = .Custom + helpViewController.modalPresentationStyle = .custom helpViewController.transitioningDelegate = self - self.window?.rootViewController?.presentViewController(helpViewController, animated: true, completion: { + self.window?.rootViewController?.present(helpViewController, animated: true, completion: { self.helpViewController.value = helpViewController - sendDispatchCompleted(observer) + sendDispatchCompleted(to: observer) }) - return NopDisposable.instance + return Disposables.create() } } func closeFulfillmentViewController() -> Observable { let close: Observable = Observable.create { observer in (self.appViewController.presentedViewController as? FulfillmentContainerViewController)?.closeFulfillmentModal() { - sendDispatchCompleted(observer) + sendDispatchCompleted(to: observer) } - return NopDisposable.instance + return Disposables.create() } return fullfilmentVisible.flatMap { visible -> Observable in @@ -190,19 +190,19 @@ private extension AppDelegate { func showWebController(address: String) -> Observable { return hideWebViewController().then ( Observable.create { observer in - let webController = ModalWebViewController(url: NSURL(string: address)!) + let webController = ModalWebViewController(url: NSURL(string: address)! as URL) let nav = UINavigationController(rootViewController: webController) - nav.modalPresentationStyle = .FormSheet + nav.modalPresentationStyle = .formSheet ARAnalytics.event("Show Web View", withProperties: ["url" : address]) - self.window?.rootViewController?.presentViewController(nav, animated: true) { - sendDispatchCompleted(observer) + self.window?.rootViewController?.present(nav, animated: true) { + sendDispatchCompleted(to: observer) } self.webViewController = nav - return NopDisposable.instance + return Disposables.create() } ) } @@ -210,30 +210,30 @@ private extension AppDelegate { func hideHelp() -> Observable { return Observable.create { observer in if let presentingViewController = self.helpViewController.value?.presentingViewController { - presentingViewController.dismissViewControllerAnimated(true) { + presentingViewController.dismiss(animated: true) { self.helpViewController.value = nil - sendDispatchCompleted(observer) + sendDispatchCompleted(to: observer) } } else { observer.onCompleted() } - return NopDisposable.instance + return Disposables.create() } } func hideWebViewController() -> Observable { return Observable.create { observer in if let webViewController = self.webViewController { - webViewController.presentingViewController?.dismissViewControllerAnimated(true) { - sendDispatchCompleted(observer) + webViewController.presentingViewController?.dismiss(animated: true) { + sendDispatchCompleted(to: observer) } } else { observer.onCompleted() } - return NopDisposable.instance + return Disposables.create() } } @@ -245,7 +245,7 @@ private extension AppDelegate { observer.onNext((self.appViewController.presentedViewController as? FulfillmentContainerViewController) != nil) observer.onCompleted() - return NopDisposable.instance + return Disposables.create() } } } diff --git a/Kiosk/App/AppViewController.swift b/Kiosk/App/AppViewController.swift index ea37a9db..a8c35493 100644 --- a/Kiosk/App/AppViewController.swift +++ b/Kiosk/App/AppViewController.swift @@ -97,12 +97,12 @@ extension AppViewController { } func auctionRequest(_ provider: Networking, auctionID: String) -> Observable { - let auctionEndpoint: ArtsyAPI = ArtsyAPI.AuctionInfo(auctionID: auctionID) + let auctionEndpoint: ArtsyAPI = ArtsyAPI.auctionInfo(auctionID: auctionID) return provider.request(auctionEndpoint) .filterSuccessfulStatusCodes() .mapJSON() - .mapToObject(Sale) + .mapTo(object: Sale.self) .logError() .retry() .throttle(1, scheduler: MainScheduler.instance) diff --git a/Kiosk/App/BidderDetailsRetrieval.swift b/Kiosk/App/BidderDetailsRetrieval.swift index 26504e2b..e3aec14b 100644 --- a/Kiosk/App/BidderDetailsRetrieval.swift +++ b/Kiosk/App/BidderDetailsRetrieval.swift @@ -6,9 +6,9 @@ import Action extension UIViewController { func promptForBidderDetailsRetrieval(provider: Networking) -> Observable { return Observable.deferred { () -> Observable in - let alertController = self.emailPromptAlertController(provider) + let alertController = self.emailPromptAlertController(provider: provider) - self.presentViewController(alertController, animated: true) { } + self.present(alertController, animated: true) { } return .empty() } @@ -21,32 +21,32 @@ extension UIViewController { SVProgressHUD.show() } .flatMap { email -> Observable in - let endpoint = ArtsyAPI.BidderDetailsNotification(auctionID: appDelegate().appViewController.sale.value.id, identifier: email) + let endpoint = ArtsyAPI.bidderDetailsNotification(auctionID: appDelegate().appViewController.sale.value.id, identifier: email) return provider.request(endpoint).filterSuccessfulStatusCodes().map(void) } .throttle(1, scheduler: MainScheduler.instance) .doOnNext { _ in SVProgressHUD.dismiss() - self.presentViewController(UIAlertController.successfulBidderDetailsAlertController(), animated: true, completion: nil) + self.present(UIAlertController.successfulBidderDetailsAlertController(), animated: true, completion: nil) } .doOnError { _ in SVProgressHUD.dismiss() - self.presentViewController(UIAlertController.failedBidderDetailsAlertController(), animated: true, completion: nil) + self.present(UIAlertController.failedBidderDetailsAlertController(), animated: true, completion: nil) } } func emailPromptAlertController(provider: Networking) -> UIAlertController { let alertController = UIAlertController(title: "Send Bidder Details", message: "Enter your email address or phone number registered with Artsy and we will send your bidder number and PIN.", preferredStyle: .alert) - let ok = UIAlertAction.Action("OK", style: .Default) + let ok = UIAlertAction.Action("OK", style: .default) let action = CocoaAction { _ -> Observable in let text = (alertController.textFields?.first)?.text ?? "" - return self.retrieveBidderDetails(provider, email: text) + return self.retrieveBidderDetails(provider: provider, email: text) } ok.rx_action = action - let cancel = UIAlertAction.Action("Cancel", style: .Cancel) + let cancel = UIAlertAction.Action("Cancel", style: .cancel) alertController.addTextField(configurationHandler: nil) alertController.addAction(ok) @@ -59,16 +59,16 @@ extension UIViewController { extension UIAlertController { class func successfulBidderDetailsAlertController() -> UIAlertController { let alertController = self.init(title: "Your details have been sent", message: nil, preferredStyle: .alert) - alertController.addAction(UIAlertAction.Action("OK", style: .Default)) + alertController.addAction(UIAlertAction.Action("OK", style: .default)) return alertController } class func failedBidderDetailsAlertController() -> UIAlertController { let alertController = self.init(title: "Incorrect Email", message: "Email was not recognized. You may not be registered to bid yet.", preferredStyle: .alert) - alertController.addAction(UIAlertAction.Action("Cancel", style: .Cancel)) + alertController.addAction(UIAlertAction.Action("Cancel", style: .cancel)) - let retryAction = UIAlertAction.Action("Retry", style: .Default) + let retryAction = UIAlertAction.Action("Retry", style: .default) retryAction.rx_action = appDelegate().requestBidderDetailsCommand() alertController.addAction(retryAction) diff --git a/Kiosk/App/Models/Artwork.swift b/Kiosk/App/Models/Artwork.swift index 7f16a8e8..bc1e196c 100644 --- a/Kiosk/App/Models/Artwork.swift +++ b/Kiosk/App/Models/Artwork.swift @@ -84,7 +84,7 @@ final class Artwork: NSObject, JSONAbleType { } if let dimensions = json["dimensions"].dictionary { - artwork.dimensions = ["in", "cm"].reduce([String](), combine: { (array, key) -> [String] in + artwork.dimensions = ["in", "cm"].reduce([String](), { (array, key) -> [String] in if let dimension = dimensions[key]?.string { return array + [dimension] } else { @@ -108,13 +108,13 @@ final class Artwork: NSObject, JSONAbleType { private func titleAndDateAttributedString(_ title: String, dateString: String) -> NSAttributedString { let workTitle = title.isEmpty ? "Untitled" : title - let workFont = UIFont.serifItalicFontWithSize(16) + let workFont = UIFont.serifItalicFont(withSize: 16) let attributedString = NSMutableAttributedString(string: workTitle, attributes: [NSFontAttributeName : workFont ]) if dateString.isNotEmpty { - let dateFont = UIFont.serifFontWithSize(16) + let dateFont = UIFont.serifFont(withSize: 16) let dateString = NSMutableAttributedString(string: ", " + dateString, attributes: [ NSFontAttributeName : dateFont ]) - attributedString.appendAttributedString(dateString) + attributedString.append(dateString) } return attributedString.copy() as! NSAttributedString diff --git a/Kiosk/App/Models/BidderPosition.swift b/Kiosk/App/Models/BidderPosition.swift index 5b88323f..7db9ff43 100644 --- a/Kiosk/App/Models/BidderPosition.swift +++ b/Kiosk/App/Models/BidderPosition.swift @@ -1,5 +1,4 @@ import Foundation -import ISO8601DateFormatter import SwiftyJSON final class BidderPosition: NSObject, JSONAbleType { @@ -21,7 +20,7 @@ final class BidderPosition: NSObject, JSONAbleType { let id = json["id"].stringValue let maxBidAmount = json["max_bid_amount_cents"].intValue - let processedAt = formatter.dateFromString(json["processed_at"].stringValue) + let processedAt = formatter.date(from: json["processed_at"].stringValue) var bid: Bid? if let bidDictionary = json["highest_bid"].object as? [String: AnyObject] { diff --git a/Kiosk/App/Models/Sale.swift b/Kiosk/App/Models/Sale.swift index b850f2e9..04c4c928 100644 --- a/Kiosk/App/Models/Sale.swift +++ b/Kiosk/App/Models/Sale.swift @@ -1,5 +1,4 @@ import UIKit -import ISO8601DateFormatter import SwiftyJSON final class Sale: NSObject, JSONAbleType { @@ -29,8 +28,8 @@ final class Sale: NSObject, JSONAbleType { let id = json["id"].stringValue let isAuction = json["is_auction"].boolValue - let startDate = formatter.dateFromString(json["start_at"].stringValue) - let endDate = formatter.dateFromString(json["end_at"].stringValue) + let startDate = formatter.date(from: json["start_at"].stringValue)! + let endDate = formatter.date(from: json["end_at"].stringValue)! let name = json["name"].stringValue let artworkCount = json["eligible_sale_artworks_count"].intValue let state = json["auction_state"].stringValue diff --git a/Kiosk/App/Models/SaleArtwork.swift b/Kiosk/App/Models/SaleArtwork.swift index 918d65c0..4e2ae61c 100644 --- a/Kiosk/App/Models/SaleArtwork.swift +++ b/Kiosk/App/Models/SaleArtwork.swift @@ -65,16 +65,16 @@ final class SaleArtwork: NSObject, JSONAbleType { } saleArtwork.auctionID = json["sale_id"].string - saleArtwork.openingBidCents = json["opening_bid_cents"].int - saleArtwork.minimumNextBidCents = json["minimum_next_bid_cents"].int + saleArtwork.openingBidCents = json["opening_bid_cents"].int as NSNumber? + saleArtwork.minimumNextBidCents = json["minimum_next_bid_cents"].int as NSNumber? - saleArtwork.highestBidCents = json["highest_bid_amount_cents"].int + saleArtwork.highestBidCents = json["highest_bid_amount_cents"].int as NSNumber? saleArtwork.estimateCents = json["estimate_cents"].int saleArtwork.lowEstimateCents = json["low_estimate_cents"].int saleArtwork.highEstimateCents = json["high_estimate_cents"].int - saleArtwork.bidCount = json["bidder_positions_count"].int + saleArtwork.bidCount = json["bidder_positions_count"].int as NSNumber? saleArtwork.reserveStatus = json["reserve_status"].string - saleArtwork.lotNumber = json["lot_number"].int + saleArtwork.lotNumber = json["lot_number"].int as NSNumber? return saleArtwork } diff --git a/Kiosk/App/Models/SaleArtworkViewModel.swift b/Kiosk/App/Models/SaleArtworkViewModel.swift index 3a8d79f9..7a01875f 100644 --- a/Kiosk/App/Models/SaleArtworkViewModel.swift +++ b/Kiosk/App/Models/SaleArtworkViewModel.swift @@ -58,7 +58,7 @@ extension SaleArtworkViewModel { // Observables representing values that change over time. func numberOfBids() -> Observable { - return saleArtwork.rx_observe(NSNumber.self, "bidCount").map { optionalBidCount -> String in + return saleArtwork.rx.observe(NSNumber.self, "bidCount").map { optionalBidCount -> String in guard let bidCount = optionalBidCount , bidCount.intValue > 0 else { return kNoBidsString } @@ -72,10 +72,10 @@ extension SaleArtworkViewModel { var numberOfBidsWithReserve: Observable { // Ignoring highestBidCents; only there to trigger on bid update. - let highestBidString = saleArtwork.rx_observe(NSNumber.self, "highestBidCents").map { "\($0)" } - let reserveStatus = saleArtwork.rx_observe(String.self, "reserveStatus").map { input -> String in + let highestBidString = saleArtwork.rx.observe(NSNumber.self, "highestBidCents").map { "\($0)" } + let reserveStatus = saleArtwork.rx.observe(String.self, "reserveStatus").map { input -> String in switch input { - case .Some(let reserveStatus): + case .some(let reserveStatus): return reserveStatus default: return "" @@ -104,7 +104,7 @@ extension SaleArtworkViewModel { } func lotNumber() -> Observable { - return saleArtwork.rx_observe(NSNumber.self, "lotNumber").map { lotNumber in + return saleArtwork.rx.observe(NSNumber.self, "lotNumber").map { lotNumber in if let lotNumber = lotNumber as? Int { return "Lot \(lotNumber)" } else { @@ -114,27 +114,27 @@ extension SaleArtworkViewModel { } func forSale() -> Observable { - return saleArtwork.artwork.rx_observe(String.self, "soldStatus").filterNil().map { status in + return saleArtwork.artwork.rx.observe(String.self, "soldStatus").filterNil().map { status in return Artwork.SoldStatus.fromString(status) == .NotSold } } func currentBid(prefix: String = "", missingPrefix: String = "") -> Observable { - return saleArtwork.rx_observe(NSNumber.self, "highestBidCents").map { [weak self] highestBidCents in + return saleArtwork.rx.observe(NSNumber.self, "highestBidCents").map { [weak self] highestBidCents in if let currentBidCents = highestBidCents as? Int { - return "\(prefix)\(NSNumberFormatter.currencyStringForCents(currentBidCents))" + return "\(prefix)\(NumberFormatter.currencyStringForCents(currentBidCents as NSNumber!))" } else { - return "\(missingPrefix)\(NSNumberFormatter.currencyStringForCents(self?.saleArtwork.openingBidCents ?? 0))" + return "\(missingPrefix)\(NumberFormatter.currencyString(forCents: self?.saleArtwork.openingBidCents ?? 0))" } } } func currentBidOrOpeningBid() -> Observable { let observables = [ - saleArtwork.rx_observe(NSNumber.self, "bidCount"), - saleArtwork.rx_observe(NSNumber.self, "openingBidCents"), - saleArtwork.rx_observe(NSNumber.self, "highestBidCents") + saleArtwork.rx.observe(NSNumber.self, "bidCount"), + saleArtwork.rx.observe(NSNumber.self, "openingBidCents"), + saleArtwork.rx.observe(NSNumber.self, "highestBidCents") ] return observables.combineLatest { numbers -> Int in @@ -147,7 +147,7 @@ extension SaleArtworkViewModel { } func currentBidOrOpeningBidLabel() -> Observable { - return saleArtwork.rx_observe(NSNumber.self, "bidCount").map { input in + return saleArtwork.rx.observe(NSNumber.self, "bidCount").map { input in guard let count = input as? Int else { return "" } if count > 0 { diff --git a/Kiosk/App/NSErrorExtensions.swift b/Kiosk/App/NSErrorExtensions.swift index 8cc1d084..1e4a9588 100644 --- a/Kiosk/App/NSErrorExtensions.swift +++ b/Kiosk/App/NSErrorExtensions.swift @@ -6,10 +6,10 @@ extension NSError { func artsyServerError() -> NSString { if let errorJSON = userInfo["data"] as? [String: AnyObject] { let error = GenericError.fromJSON(errorJSON) - return "\(error.message) - \(error.detail) + \(error.detail)" + return "\(error.message) - \(error.detail) + \(error.detail)" as NSString } else if let response = userInfo["data"] as? Response { - let stringData = NSString(data: response.data, encoding: String.Encoding.utf8) - return "Status Code: \(response.statusCode), Data Length: \(response.data.length), String Data: \(stringData)" + let stringData = NSString(data: response.data, encoding: String.Encoding.utf8.rawValue) + return "Status Code: \(response.statusCode), Data Length: \(response.data.length), String Data: \(stringData)" as NSString } return "\(userInfo)" as NSString diff --git a/Kiosk/App/UIViewSubclassesErrorExtensions.swift b/Kiosk/App/UIViewSubclassesErrorExtensions.swift index a2f8bc0c..b938e30e 100644 --- a/Kiosk/App/UIViewSubclassesErrorExtensions.swift +++ b/Kiosk/App/UIViewSubclassesErrorExtensions.swift @@ -6,16 +6,16 @@ extension Button { let originalTitle = self.title(for: UIControlState()) setTitleColor(.white, for: .disabled) - setBackgroundColor(.artsyRed(), for: .disabled, animated: true) - setBorderColor(.artsyRed(), for: .disabled, animated: true) + setBackgroundColor(.artsyRedRegular(), for: .disabled, animated: true) + setBorderColor(.artsyRedRegular(), for: .disabled, animated: true) setTitle(message.uppercased(), for: .disabled) delayToMainThread(2) { - self.setTitleColor(.artsyMediumGrey(), for: .disabled) + self.setTitleColor(.artsyGrayMedium(), for: .disabled) self.setBackgroundColor(.white, for: .disabled, animated: true) self.setTitle(originalTitle, for: .disabled) - self.setBorderColor(.artsyMediumGrey(), for: .disabled, animated: true) + self.setBorderColor(.artsyGrayMedium(), for: .disabled, animated: true) } } } @@ -23,9 +23,9 @@ extension Button { extension TextField { func flashForError() { - self.setBorderColor(.artsyRed()) + self.setBorderColor(.artsyRedRegular()) delayToMainThread(2) { - self.setBorderColor(.artsyPurple()) + self.setBorderColor(.artsyPurpleRegular()) } } } diff --git a/Kiosk/App/Views/Button Subclasses/Button.swift b/Kiosk/App/Views/Button Subclasses/Button.swift index b048c3a5..35a15ca7 100644 --- a/Kiosk/App/Views/Button Subclasses/Button.swift +++ b/Kiosk/App/Views/Button Subclasses/Button.swift @@ -23,16 +23,16 @@ class ActionButton: Button { super.setup() setBorderColor(.black, for: UIControlState(), animated: false) - setBorderColor(.artsyPurple(), for: .highlighted, animated: false) - setBorderColor(.artsyMediumGrey(), for: .disabled, animated: false) + setBorderColor(.artsyPurpleRegular(), for: .highlighted, animated: false) + setBorderColor(.artsyGrayMedium(), for: .disabled, animated: false) setBackgroundColor(.black, for: UIControlState(), animated: false) - setBackgroundColor(.artsyPurple(), for: .highlighted, animated: false) + setBackgroundColor(.artsyPurpleRegular(), for: .highlighted, animated: false) setBackgroundColor(.white, for: .disabled, animated: false) setTitleColor(.white, for:UIControlState()) setTitleColor(.white, for:.highlighted) - setTitleColor(.artsyHeavyGrey(), for:.disabled) + setTitleColor(UIColor.artsyGrayBold(), for:.disabled) } } @@ -44,18 +44,18 @@ class SecondaryActionButton: Button { override func setup() { super.setup() - - setBorderColor(.artsyMediumGrey(), for: UIControlState(), animated: false) - setBorderColor(.artsyPurple(), for: .highlighted, animated: false) - setBorderColor(.artsyLightGrey(), for: .disabled, animated: false) + + setBorderColor(.artsyGrayMedium(), for: UIControlState(), animated: false) + setBorderColor(.artsyPurpleRegular(), for: .highlighted, animated: false) + setBorderColor(.artsyGrayLight(), for: .disabled, animated: false) setBackgroundColor(.white, for: UIControlState(), animated: false) - setBackgroundColor(.artsyPurple(), for: .highlighted, animated: false) + setBackgroundColor(.artsyPurpleRegular(), for: .highlighted, animated: false) setBackgroundColor(.white, for: .disabled, animated: false) setTitleColor(.black, for:UIControlState()) setTitleColor(.white, for:.highlighted) - setTitleColor(.artsyHeavyGrey(), for:.disabled) + setTitleColor(.artsyGrayBold(), for:.disabled) } } diff --git a/Kiosk/App/Views/RegisterFlowView.swift b/Kiosk/App/Views/RegisterFlowView.swift index a114edc6..de54484f 100644 --- a/Kiosk/App/Views/RegisterFlowView.swift +++ b/Kiosk/App/Views/RegisterFlowView.swift @@ -17,7 +17,7 @@ class RegisterFlowView: ORStackView { override func awakeFromNib() { super.awakeFromNib() - backgroundColor = .whiteColor() + backgroundColor = .white bottomMarginHeight = CGFloat(NSNotFound) updateConstraints() } @@ -50,7 +50,7 @@ class RegisterFlowView: ORStackView { itemView.createInfoLabel(value) let button = itemView.createJumpToButtonAtIndex(i) - button.addTarget(self, action: #selector(pressed(_:)), forControlEvents: .TouchUpInside) + button.addTarget(self, action: #selector(pressed(_:)), for: .touchUpInside) itemView.constrainHeight("44") } else { @@ -63,7 +63,7 @@ class RegisterFlowView: ORStackView { } let spacer = UIView(frame: bounds) - spacer.setContentHuggingPriority(12, forAxis: .Horizontal) + spacer.setContentHuggingPriority(12, for: .horizontal) addSubview(spacer, withTopMargin: "0", sideMargin: "0") bottomMarginHeight = 0 @@ -78,7 +78,7 @@ class RegisterFlowView: ORStackView { var titleLabel: UILabel? func highlight() { - titleLabel?.textColor = .artsyPurple() + titleLabel?.textColor = .artsyPurpleRegular() } func createTitleViewWithTitle(_ title: String) { diff --git a/Kiosk/App/Views/Spinner.swift b/Kiosk/App/Views/Spinner.swift index 6ed0c612..fc689a19 100644 --- a/Kiosk/App/Views/Spinner.swift +++ b/Kiosk/App/Views/Spinner.swift @@ -6,14 +6,14 @@ class Spinner: UIView { func createSpinner() -> UIView { let view = UIView(frame: CGRect(x: 0, y: 0, width: 20, height: 5)) - view.backgroundColor = .black() + view.backgroundColor = .black return view } override func awakeFromNib() { spinner = createSpinner() addSubview(spinner) - backgroundColor = .clear() + backgroundColor = .clear animateN(Float.infinity) } diff --git a/Kiosk/App/Views/SwitchView.swift b/Kiosk/App/Views/SwitchView.swift index 8ba7e307..fb05ab87 100644 --- a/Kiosk/App/Views/SwitchView.swift +++ b/Kiosk/App/Views/SwitchView.swift @@ -38,7 +38,7 @@ class SwitchView: UIView { button.backgroundColor = .white button.setTitleColor(.black, for: .disabled) button.setTitleColor(.black, for: .selected) - button.setTitleColor(.artsyMediumGrey(), for: UIControlState()) + button.setTitleColor(.artsyGrayMedium(), for: UIControlState()) return button } @@ -107,8 +107,8 @@ private extension SwitchView { button.alignTop("\(SwitchViewBorderWidth)", bottom: "\(-SwitchViewBorderWidth)", to: self) } - topBar.backgroundColor = UIColor.artsyMediumGrey().cgColor - bottomBar.backgroundColor = UIColor.artsyMediumGrey().cgColor + topBar.backgroundColor = UIColor.artsyGrayMedium().cgColor + bottomBar.backgroundColor = UIColor.artsyGrayMedium().cgColor layer.addSublayer(topBar) layer.addSublayer(bottomBar) diff --git a/Kiosk/App/Views/Text Fields/TextField.swift b/Kiosk/App/Views/Text Fields/TextField.swift index c4c37c43..a4c8ed60 100644 --- a/Kiosk/App/Views/Text Fields/TextField.swift +++ b/Kiosk/App/Views/Text Fields/TextField.swift @@ -25,7 +25,7 @@ class TextField: UITextField { layer.cornerRadius = 0 layer.masksToBounds = true layer.borderWidth = 1 - tintColor = .black() + tintColor = .black stateChangedAnimated(false) setupEvents() } @@ -45,7 +45,7 @@ class TextField: UITextField { func stateChangedAnimated(_ animated: Bool) { let newBorderColor = borderColorForState().cgColor - if CGColorEqualToColor(newBorderColor, layer.borderColor) { + if newBorderColor == layer.borderColor { return } if animated { @@ -61,9 +61,9 @@ class TextField: UITextField { func borderColorForState() -> UIColor { if isEditing && shouldChangeColorWhenEditing { - return .artsyPurple() + return .artsyPurpleRegular() } else { - return .artsyMediumGrey() + return .artsyGrayMedium() } } diff --git a/Kiosk/Auction Listings/ListingsCountdownManager.swift b/Kiosk/Auction Listings/ListingsCountdownManager.swift index e5862060..161c40ad 100644 --- a/Kiosk/Auction Listings/ListingsCountdownManager.swift +++ b/Kiosk/Auction Listings/ListingsCountdownManager.swift @@ -76,9 +76,10 @@ class ListingsCountdownManager: NSObject { if sale.isActive(time) { let now = time.date() - let components = Calendar.currentCalendar().components([.Hour, .Minute, .Second], fromDate: now, toDate: sale.endDate, options: []) - self.countdownLabel.text = "\(formatter.stringFromNumber(components.hour)!) : \(formatter.stringFromNumber(components.minute)!) : \(formatter.stringFromNumber(components.second)!)" + let components = Calendar.current.dateComponents([.hour, .minute, .second], from: now, to: sale.endDate) + + self.countdownLabel.text = "\(formatter.string(from: (components.hour ?? 0) as NSNumber)) : \(formatter.string(from: (components.minute ?? 0) as NSNumber)) : \(formatter.string(from: (components.second ?? 0) as NSNumber))" } else { self.countdownLabel.text = "CLOSED" diff --git a/Kiosk/Auction Listings/ListingsViewModel.swift b/Kiosk/Auction Listings/ListingsViewModel.swift index 55800d32..bf1859c2 100644 --- a/Kiosk/Auction Listings/ListingsViewModel.swift +++ b/Kiosk/Auction Listings/ListingsViewModel.swift @@ -133,7 +133,7 @@ class ListingsViewModel: NSObject, ListingsViewModelType { // Repeatedly calls itself with page+1 until the count of the returned array is < pageSize. fileprivate func retrieveAllListingsRequest(_ page: Int) -> Observable { return Observable.create { [weak self] observer in - guard let me = self else { return NopDisposable.instance } + guard let me = self else { return Disposables.create() } return me.listingsRequest(forPage: page).subscribeNext { object in guard let array = object as? Array else { return } diff --git a/Kiosk/Auction Listings/MasonryCollectionViewCell.swift b/Kiosk/Auction Listings/MasonryCollectionViewCell.swift index 6b3fa645..1ff4dd80 100644 --- a/Kiosk/Auction Listings/MasonryCollectionViewCell.swift +++ b/Kiosk/Auction Listings/MasonryCollectionViewCell.swift @@ -49,9 +49,9 @@ class MasonryCollectionViewCell: ListingsCollectionViewCell { viewModel.flatMapTo(SaleArtworkViewModel.lotNumber) .subscribeNext { (lotNumber)in switch lotNumber { - case .Some(let text) where text.isEmpty: + case .some(let text) where text.isEmpty: fallthrough - case .None: + case .none: lotNumberTopConstraint.constant = 0 artistNameTopConstraint.constant = 20 default: @@ -67,7 +67,7 @@ class MasonryCollectionViewCell: ListingsCollectionViewCell { if let artworkImageViewHeightConstraint = self?.artworkImageViewHeightConstraint { self?.artworkImageView.removeConstraint(artworkImageViewHeightConstraint) } - let imageHeight = heightForImageWithAspectRatio(viewModel.thumbnailAspectRatio) + let imageHeight = heightForImage(withAspectRatio: viewModel.thumbnailAspectRatio) self?.artworkImageViewHeightConstraint = self?.artworkImageView.constrainHeight("\(imageHeight)").first as? NSLayoutConstraint self?.layoutIfNeeded() } @@ -76,13 +76,13 @@ class MasonryCollectionViewCell: ListingsCollectionViewCell { override func layoutSubviews() { super.layoutSubviews() - bidView.drawTopDottedBorder(with: .artsyMediumGrey()) + bidView.drawTopDottedBorder(with: .artsyGrayMedium()) } } extension MasonryCollectionViewCell { class func heightForCellWithImageAspectRatio(_ aspectRatio: CGFloat?) -> CGFloat { - let imageHeight = heightForImageWithAspectRatio(aspectRatio) + let imageHeight = heightForImage(withAspectRatio: aspectRatio) let remainingHeight = 20 + // padding 20 + // artist name diff --git a/Kiosk/Auction Listings/TableCollectionViewCell.swift b/Kiosk/Auction Listings/TableCollectionViewCell.swift index 75c75f68..c1afaa50 100644 --- a/Kiosk/Auction Listings/TableCollectionViewCell.swift +++ b/Kiosk/Auction Listings/TableCollectionViewCell.swift @@ -57,14 +57,14 @@ class TableCollectionViewCell: ListingsCollectionViewCell { // Replaces the observable defined in the superclass, normally used to emit taps to a "More Info" label, which we don't have. let recognizer = UITapGestureRecognizer() contentView.addGestureRecognizer(recognizer) - self.moreInfo = recognizer.rx_event.map { _ -> Date in + self.moreInfo = recognizer.rx.event.map { _ -> Date in return Date() } } override func layoutSubviews() { super.layoutSubviews() - contentView.drawBottomSolidBorder(with: .artsyMediumGrey()) + contentView.drawBottomSolidBorder(with: .artsyGrayMedium()) } } diff --git a/Kiosk/Auction Listings/WebViewController.swift b/Kiosk/Auction Listings/WebViewController.swift index 24d2a1c0..42b8915d 100644 --- a/Kiosk/Auction Listings/WebViewController.swift +++ b/Kiosk/Auction Listings/WebViewController.swift @@ -35,7 +35,7 @@ class ModalWebViewController: WebViewController { closeButton = UIButton() view.addSubview(closeButton) closeButton.titleLabel?.font = UIFont.sansSerifFont(withSize: 14) - closeButton.setTitleColor(.artsyMediumGrey(), for:UIControlState()) + closeButton.setTitleColor(.artsyGrayMedium(), for:UIControlState()) closeButton.setTitle("CLOSE", for:UIControlState()) closeButton.constrainWidth("140", height: "72") closeButton.alignTop("0", leading:"0", bottom:nil, trailing:nil, to:view) diff --git a/Kiosk/Bid Fulfillment/AdminCCBypassNetworkModel.swift b/Kiosk/Bid Fulfillment/AdminCCBypassNetworkModel.swift index cdce372e..4212cc8b 100644 --- a/Kiosk/Bid Fulfillment/AdminCCBypassNetworkModel.swift +++ b/Kiosk/Bid Fulfillment/AdminCCBypassNetworkModel.swift @@ -17,22 +17,22 @@ class AdminCCBypassNetworkModel: AdminCCBypassNetworkModelType { /// Returns an Observable of (Bool, AuthorizedNetworking) /// The Bool represents if the Credit Card requirement should be waived. /// THe AuthorizedNetworking is the same instance that's passed in, which is a convenience for chaining observables. - func checkForAdminCCBypass(saleID: String, authorizedNetworking: AuthorizedNetworking) -> Observable { + func checkForAdminCCBypass(_ saleID: String, authorizedNetworking: AuthorizedNetworking) -> Observable { return authorizedNetworking - .request(ArtsyAuthenticatedAPI.FindMyBidderRegistration(auctionID: saleID)) + .request(ArtsyAuthenticatedAPI.findMyBidderRegistration(auctionID: saleID)) .filterSuccessfulStatusCodes() .mapJSON() - .mapToObjectArray(Bidder) + .mapTo(arrayOf: Bidder.self) .map { bidders in return bidders.first } .map { bidder -> BypassResult in - guard let bidder = bidder else { return .RequireCC } + guard let bidder = bidder else { return .requireCC } switch bidder.createdByAdmin { - case true: return .SkipCCRequirement - case false: return .RequireCC + case true: return .skipCCRequirement + case false: return .requireCC } } .logError() diff --git a/Kiosk/Bid Fulfillment/BidCheckingNetworkModel.swift b/Kiosk/Bid Fulfillment/BidCheckingNetworkModel.swift index 5733db1b..183127dd 100644 --- a/Kiosk/Bid Fulfillment/BidCheckingNetworkModel.swift +++ b/Kiosk/Bid Fulfillment/BidCheckingNetworkModel.swift @@ -6,7 +6,7 @@ enum BidCheckingError: String { case PollingExceeded } -extension BidCheckingError: Error { } +extension BidCheckingError: Swift.Error { } protocol BidCheckingNetworkModelType { var bidDetails: BidDetails { get } @@ -42,7 +42,7 @@ class BidCheckingNetworkModel: NSObject, BidCheckingNetworkModelType { func waitForBidResolution (bidderPositionId: String, provider: AuthorizedNetworking) -> Observable { return self - .pollForUpdatedBidderPosition(bidderPositionId, provider: provider) + .poll(forUpdatedBidderPosition: bidderPositionId, provider: provider) .then { return self.getUpdatedSaleArtwork() @@ -70,7 +70,7 @@ class BidCheckingNetworkModel: NSObject, BidCheckingNetworkModelType { } fileprivate func poll(forUpdatedBidderPosition bidderPositionId: String, provider: AuthorizedNetworking) -> Observable { - let updatedBidderPosition = getUpdatedBidderPosition(bidderPositionId, provider: provider) + let updatedBidderPosition = getUpdatedBidderPosition(bidderPositionId: bidderPositionId, provider: provider) .flatMap { bidderPositionObject -> Observable in self.pollRequests += 1 @@ -92,7 +92,7 @@ class BidCheckingNetworkModel: NSObject, BidCheckingNetworkModelType { .take(1) .map(void) .then { - return self.pollForUpdatedBidderPosition(bidderPositionId, provider: provider) + return self.poll(forUpdatedBidderPosition: bidderPositionId, provider: provider) } } } @@ -104,7 +104,7 @@ class BidCheckingNetworkModel: NSObject, BidCheckingNetworkModelType { } fileprivate func checkForMaxBid(provider: AuthorizedNetworking) -> Observable { - return getMyBidderPositions(provider) + return getMyBidderPositions(provider: provider) .doOnNext{ newBidderPositions in if let topBidID = self.mostRecentSaleArtwork?.saleHighestBid?.id { @@ -125,7 +125,7 @@ class BidCheckingNetworkModel: NSObject, BidCheckingNetworkModelType { .request(endpoint) .filterSuccessfulStatusCodes() .mapJSON() - .mapToObjectArray(BidderPosition) + .mapTo(arrayOf: BidderPosition.self) } fileprivate func getUpdatedSaleArtwork() -> Observable { @@ -138,15 +138,15 @@ class BidCheckingNetworkModel: NSObject, BidCheckingNetworkModelType { .request(endpoint) .filterSuccessfulStatusCodes() .mapJSON() - .mapToObject(SaleArtwork) + .mapTo(object: SaleArtwork.self) } fileprivate func getUpdatedBidderPosition(bidderPositionId: String, provider: AuthorizedNetworking) -> Observable { - let endpoint = ArtsyAuthenticatedAPI.MyBidPosition(id: bidderPositionId) + let endpoint = ArtsyAuthenticatedAPI.myBidPosition(id: bidderPositionId) return provider .request(endpoint) .filterSuccessfulStatusCodes() .mapJSON() - .mapToObject(BidderPosition) + .mapTo(object: BidderPosition.self) } } diff --git a/Kiosk/Bid Fulfillment/ConfirmYourBidArtsyLoginViewController.swift b/Kiosk/Bid Fulfillment/ConfirmYourBidArtsyLoginViewController.swift index ace5f951..6192689d 100644 --- a/Kiosk/Bid Fulfillment/ConfirmYourBidArtsyLoginViewController.swift +++ b/Kiosk/Bid Fulfillment/ConfirmYourBidArtsyLoginViewController.swift @@ -38,8 +38,8 @@ class ConfirmYourBidArtsyLoginViewController: UIViewController { emailTextField.text = nav.bidDetails.newUser.email.value ?? "" - let emailText = emailTextField.rx_text.takeUntil(viewWillDisappear) - let passwordText = passwordTextField.rx_text.takeUntil(viewWillDisappear) + let emailText = emailTextField.rx.text.takeUntil(viewWillDisappear) + let passwordText = passwordTextField.rx.text.takeUntil(viewWillDisappear) emailText .mapToOptional() @@ -52,7 +52,7 @@ class ConfirmYourBidArtsyLoginViewController: UIViewController { .addDisposableTo(rx_disposeBag) let inputIsEmail = emailText.map(stringIsEmailAddress) - let passwordIsLongEnough = passwordText.map(isZeroLengthString).not() + let passwordIsLongEnough = passwordText.map(isZeroLength).not() let formIsValid = [inputIsEmail, passwordIsLongEnough].combineLatestAnd() let provider = self.provider @@ -60,11 +60,11 @@ class ConfirmYourBidArtsyLoginViewController: UIViewController { confirmCredentialsButton.rx_action = CocoaAction(enabledIf: formIsValid) { [weak self] _ -> Observable in guard let me = self else { return .empty() } - return bidDetails.authenticatedNetworking(provider) + return bidDetails.authenticatedNetworking(provider: provider!) .flatMap { provider -> Observable in return me.fulfillmentNav() - .updateUserCredentials(provider) - .mapReplace(provider) + .updateUserCredentials(loggedInProvider: provider) + .mapReplace(with: provider) }.flatMap { provider -> Observable in return me.creditCard(provider) .doOnNext { cards in @@ -122,10 +122,10 @@ class ConfirmYourBidArtsyLoginViewController: UIViewController { @IBAction func forgotPasswordTapped(_ sender: AnyObject) { let alertController = UIAlertController(title: "Forgot Password", message: "Please enter your email address and we'll send you a reset link.", preferredStyle: .alert) - let submitAction = UIAlertAction.Action("Send", style: .Default) + let submitAction = UIAlertAction.Action("Send", style: .default) let email = Variable("") submitAction.rx_action = CocoaAction(enabledIf: email.asObservable().map(stringIsEmailAddress), workFactory: { () -> Observable in - let endpoint: ArtsyAPI = ArtsyAPI.LostPasswordNotification(email: email.value) + let endpoint: ArtsyAPI = ArtsyAPI.lostPasswordNotification(email: email.value) return self.provider.request(endpoint) .filterSuccessfulStatusCodes() @@ -142,12 +142,12 @@ class ConfirmYourBidArtsyLoginViewController: UIViewController { textField.text = self.emailTextField.text textField - .rx_text + .rx.text .bindTo(email) .addDisposableTo(textField.rx_disposeBag) - NotificationCenter.defaultCenter().addObserverForName(NSNotification.Name.UITextFieldTextDidChange, object: textField, queue: OperationQueue.mainQueue()) { (notification) in - submitAction.enabled = stringIsEmailAddress(textField.text ?? "").boolValue + NotificationCenter.default.addObserver(forName: NSNotification.Name.UITextFieldTextDidChange, object: textField, queue: OperationQueue.main) { (notification) in + submitAction.isEnabled = stringIsEmailAddress(textField.text ?? "").boolValue } } @@ -163,7 +163,7 @@ class ConfirmYourBidArtsyLoginViewController: UIViewController { .request(endpoint) .filterSuccessfulStatusCodes() .mapJSON() - .mapToObjectArray(Card.self) + .mapTo(arrayOf: Card.self) } @IBAction func useBidderTapped(_ sender: AnyObject) { diff --git a/Kiosk/Bid Fulfillment/ConfirmYourBidPINViewController.swift b/Kiosk/Bid Fulfillment/ConfirmYourBidPINViewController.swift index f22ebc47..575689f0 100644 --- a/Kiosk/Bid Fulfillment/ConfirmYourBidPINViewController.swift +++ b/Kiosk/Bid Fulfillment/ConfirmYourBidPINViewController.swift @@ -30,7 +30,7 @@ class ConfirmYourBidPINViewController: UIViewController { .addDisposableTo(rx_disposeBag) pin - .bindTo(pinTextField.rx_text) + .bindTo(pinTextField.rx.text) .addDisposableTo(rx_disposeBag) pin @@ -51,21 +51,21 @@ class ConfirmYourBidPINViewController: UIViewController { var loggedInProvider: AuthorizedNetworking! - return bidDetails.authenticatedNetworking(provider) + return bidDetails.authenticatedNetworking(provider: provider!) .doOnNext { provider in loggedInProvider = provider } .flatMap { provider -> Observable in return provider - .request(ArtsyAuthenticatedAPI.Me) + .request(ArtsyAuthenticatedAPI.me) .filterSuccessfulStatusCodes() - .mapReplace(provider) + .mapReplace(with: provider) } .flatMap { provider -> Observable in return me .fulfillmentNav() - .updateUserCredentials(loggedInProvider) - .mapReplace(provider) + .updateUserCredentials(loggedInProvider: loggedInProvider) + .mapReplace(with: provider) } .flatMap { provider -> Observable in return me @@ -74,14 +74,14 @@ class ConfirmYourBidPINViewController: UIViewController { .flatMap { result -> Observable in switch result { - case .SkipCCRequirement: + case .skipCCRequirement: // We should bypass the CC requirement and move directly onto placing the bid. me.performSegue(.PINConfirmedhasCard) return .empty() - case .RequireCC: + case .requireCC: // We must check for a CC, and collect one if necessary. return me - .checkForCreditCard(provider) + .checkForCreditCard(loggedInProvider: provider) .doOnNext(me.gotCards) .map(void) } @@ -89,7 +89,7 @@ class ConfirmYourBidPINViewController: UIViewController { } .doOnError { error in if let response = (error as? Moya.Error)?.response { - let responseBody = NSString(data: response.data, encoding: NSUTF8StringEncoding) + let responseBody = NSString(data: response.data, encoding: String.Encoding.utf8.rawValue) print("Error authenticating(\(response.statusCode)): \(responseBody)") } @@ -111,9 +111,9 @@ class ConfirmYourBidPINViewController: UIViewController { } @IBAction func forgotPINTapped(_ sender: AnyObject) { - let auctionID = fulfillmentNav().auctionID + let auctionID = fulfillmentNav().auctionID ?? "" let number = fulfillmentNav().bidDetails.newUser.phoneNumber.value ?? "" - let endpoint: ArtsyAPI = ArtsyAPI.BidderDetailsNotification(auctionID: auctionID, identifier: number) + let endpoint: ArtsyAPI = ArtsyAPI.bidderDetailsNotification(auctionID: auctionID, identifier: number) let alertController = UIAlertController(title: "Forgot PIN", message: "We have sent your bidder details to your device.", preferredStyle: .alert) diff --git a/Kiosk/Bid Fulfillment/ConfirmYourBidViewController.swift b/Kiosk/Bid Fulfillment/ConfirmYourBidViewController.swift index cdef851b..0ecdb8c0 100644 --- a/Kiosk/Bid Fulfillment/ConfirmYourBidViewController.swift +++ b/Kiosk/Bid Fulfillment/ConfirmYourBidViewController.swift @@ -45,7 +45,7 @@ class ConfirmYourBidViewController: UIViewController { number .map(toPhoneNumberString) - .bindTo(numberAmountTextField.rx_text) + .bindTo(numberAmountTextField.rx.text) .addDisposableTo(rx_disposeBag) let nav = self.fulfillmentNav() @@ -65,12 +65,12 @@ class ConfirmYourBidViewController: UIViewController { let auctionID = nav.auctionID - let numberIsZeroLength = number.map(isZeroLengthString) + let numberIsZeroLength = number.map(isZeroLength) enterButton.rx_action = CocoaAction(enabledIf: numberIsZeroLength.not(), workFactory: { [weak self] _ in guard let me = self else { return .empty() } - let endpoint = ArtsyAPI.FindBidderRegistration(auctionID: auctionID, phone: String(me._number.value)) + let endpoint = ArtsyAPI.findBidderRegistration(auctionID: auctionID, phone: String(me._number.value)) return me.provider.request(endpoint) .filterStatusCode(400) diff --git a/Kiosk/Bid Fulfillment/FulfillmentNavigationController.swift b/Kiosk/Bid Fulfillment/FulfillmentNavigationController.swift index e3ff00d9..a882bb0a 100644 --- a/Kiosk/Bid Fulfillment/FulfillmentNavigationController.swift +++ b/Kiosk/Bid Fulfillment/FulfillmentNavigationController.swift @@ -37,7 +37,7 @@ class FulfillmentNavigationController: UINavigationController, FulfillmentContro func updateUserCredentials(loggedInProvider: AuthorizedNetworking) -> Observable { let endpoint = ArtsyAuthenticatedAPI.me - let request = loggedInProvider.request(endpoint).filterSuccessfulStatusCodes().mapJSON().mapToObject(User) + let request = loggedInProvider.request(endpoint).filterSuccessfulStatusCodes().mapJSON().mapTo(object: User.self) return request .doOnNext { [weak self] fullUser in @@ -52,7 +52,7 @@ class FulfillmentNavigationController: UINavigationController, FulfillmentContro newUser.zipCode.value = me.user.location?.postalCode newUser.name.value = me.user.name } - .logError("error, the authentication for admin is likely wrong: ") + .logError(prefix: "error, the authentication for admin is likely wrong: ") .map(void) } } diff --git a/Kiosk/Bid Fulfillment/GenericFormValidationViewModel.swift b/Kiosk/Bid Fulfillment/GenericFormValidationViewModel.swift index b46275bd..b8128e89 100644 --- a/Kiosk/Bid Fulfillment/GenericFormValidationViewModel.swift +++ b/Kiosk/Bid Fulfillment/GenericFormValidationViewModel.swift @@ -14,7 +14,7 @@ class GenericFormValidationViewModel { finishedSubject.onCompleted() observer.onCompleted() - return NopDisposable.instance + return Disposables.create() } } @@ -24,4 +24,4 @@ class GenericFormValidationViewModel { } .addDisposableTo(disposeBag) } -} \ No newline at end of file +} diff --git a/Kiosk/Bid Fulfillment/KeypadViewModel.swift b/Kiosk/Bid Fulfillment/KeypadViewModel.swift index d2fbdb28..d07f937b 100644 --- a/Kiosk/Bid Fulfillment/KeypadViewModel.swift +++ b/Kiosk/Bid Fulfillment/KeypadViewModel.swift @@ -42,11 +42,11 @@ private extension KeypadViewModel { strongSelf.intValue.value = Int(strongSelf.intValue.value / 10) if strongSelf.stringValue.value.isNotEmpty { let string = strongSelf.stringValue.value - strongSelf.stringValue.value = string.substring(to: string.endIndex.predecessor()) + strongSelf.stringValue.value = string.substring(to: string.index(before: string.endIndex)) } } observer.onCompleted() - return NopDisposable.instance + return Disposables.create() } } @@ -55,7 +55,7 @@ private extension KeypadViewModel { self?.intValue.value = 0 self?.stringValue.value = "" observer.onCompleted() - return NopDisposable.instance + return Disposables.create() } } @@ -69,7 +69,7 @@ private extension KeypadViewModel { strongSelf.stringValue.value = "\(strongSelf.stringValue.value)\(input)" } observer.onCompleted() - return NopDisposable.instance + return Disposables.create() } } } diff --git a/Kiosk/Bid Fulfillment/LoadingViewController.swift b/Kiosk/Bid Fulfillment/LoadingViewController.swift index 35c7845f..a6a17493 100644 --- a/Kiosk/Bid Fulfillment/LoadingViewController.swift +++ b/Kiosk/Bid Fulfillment/LoadingViewController.swift @@ -193,7 +193,8 @@ extension LoadingViewController { func handleLowestBidder() { titleLabel.text = "Higher bid needed" - titleLabel.textColor = .artsyRed() + + titleLabel.textColor = .artsyRedRegular() statusMessage.isHidden = false statusMessage.text = "Another bidder has placed a higher maximum bid. Place a higher bid to secure the lot." bidConfirmationImageView.image = UIImage(named: "BidNotHighestBidder") @@ -229,7 +230,7 @@ extension LoadingViewController { } func handleError(withTitle title: String, message: String, error: NSError) { - titleLabel.textColor = .artsyRed() + titleLabel.textColor = .artsyRedRegular() titleLabel.text = title statusMessage.text = message statusMessage.isHidden = false diff --git a/Kiosk/Bid Fulfillment/LoadingViewModel.swift b/Kiosk/Bid Fulfillment/LoadingViewModel.swift index bec74206..8d38fbe7 100644 --- a/Kiosk/Bid Fulfillment/LoadingViewModel.swift +++ b/Kiosk/Bid Fulfillment/LoadingViewModel.swift @@ -82,7 +82,7 @@ class LoadingViewModel: NSObject, LoadingViewModelType { guard let me = self else { return .empty() } guard me.placingBid else { return .empty() } - return me.bidCheckingModel.waitForBidResolution(tuple.0, provider: tuple.1).map(void) + return me.bidCheckingModel.waitForBidResolution(bidderPositionId: tuple.0, provider: tuple.1).map(void) } } } diff --git a/Kiosk/Bid Fulfillment/ManualCreditCardInputViewController.swift b/Kiosk/Bid Fulfillment/ManualCreditCardInputViewController.swift index eaf54df4..b0d261bf 100644 --- a/Kiosk/Bid Fulfillment/ManualCreditCardInputViewController.swift +++ b/Kiosk/Bid Fulfillment/ManualCreditCardInputViewController.swift @@ -39,36 +39,36 @@ class ManualCreditCardInputViewController: UIViewController, RegistrationSubCont viewModel .cardFullDigits .asObservable() - .bindTo(cardNumberTextField.rx_text) + .bindTo(cardNumberTextField.rx.text) .addDisposableTo(rx_disposeBag) viewModel .expirationYear .asObservable() - .bindTo(expirationYearTextField.rx_text) + .bindTo(expirationYearTextField.rx.text) .addDisposableTo(rx_disposeBag) viewModel .expirationMonth .asObservable() - .bindTo(expirationMonthTextField.rx_text) + .bindTo(expirationMonthTextField.rx.text) .addDisposableTo(rx_disposeBag) viewModel .securityCode .asObservable() - .bindTo(securitycodeTextField.rx_text) + .bindTo(securitycodeTextField.rx.text) .addDisposableTo(rx_disposeBag) viewModel .billingZip .asObservable() - .bindTo(billingZipTextField.rx_text) + .bindTo(billingZipTextField.rx.text) .addDisposableTo(rx_disposeBag) viewModel .creditCardNumberIsValid - .bindTo(cardConfirmButton.rx_enabled) + .bindTo(cardConfirmButton.rx.enabled) .addDisposableTo(rx_disposeBag) let action = viewModel.registerButtonCommand() @@ -77,7 +77,7 @@ class ManualCreditCardInputViewController: UIViewController, RegistrationSubCont action .errors // Based on errors .take(1) // On the first error, then forever - .mapReplace(false) // Replace the error with false + .mapReplace(with: false) // Replace the error with false .startWith(true) // But begin with true .bindTo(billingZipErrorLabel.rx_hidden) // show the error label .addDisposableTo(rx_disposeBag) diff --git a/Kiosk/Bid Fulfillment/ManualCreditCardInputViewModel.swift b/Kiosk/Bid Fulfillment/ManualCreditCardInputViewModel.swift index bfcbe2c0..bc7f1f72 100644 --- a/Kiosk/Bid Fulfillment/ManualCreditCardInputViewModel.swift +++ b/Kiosk/Bid Fulfillment/ManualCreditCardInputViewModel.swift @@ -30,18 +30,18 @@ class ManualCreditCardInputViewModel: NSObject { } var expiryDatesAreValid: Observable { - let month = expirationMonth.asObservable().map(isStringLengthIn(1..<3)) - let year = expirationYear.asObservable().map(isStringLengthOneOf([2,4])) + let month = expirationMonth.asObservable().map(isStringLength(in: 1..<3)) + let year = expirationYear.asObservable().map(isStringLength(oneOf: [2,4])) return [month, year].combineLatestAnd() } var securityCodeIsValid: Observable { - return securityCode.asObservable().map(isStringLengthIn(3..<5)) + return securityCode.asObservable().map(isStringLength(in: 3..<5)) } var billingZipIsValid: Observable { - return billingZip.asObservable().map(isStringLengthIn(4..<8)) + return billingZip.asObservable().map(isStringLength(in: 4..<8)) } var moveToYear: Observable { @@ -59,7 +59,7 @@ class ManualCreditCardInputViewModel: NSObject { return .empty() } - return me.registerCard(newUser).doOnCompleted { + return me.registerCard(newUser: newUser).doOnCompleted { me.finishedSubject?.onCompleted() }.map(void) } @@ -78,10 +78,10 @@ class ManualCreditCardInputViewModel: NSObject { /// MARK: - Private Methods fileprivate func registerCard(newUser: NewUser) -> Observable { - let month = expirationMonth.value.toUIntWithDefault(0) - let year = expirationYear.value.toUIntWithDefault(0) + let month = expirationMonth.value.toUInt(withDefault: 0) + let year = expirationYear.value.toUInt(withDefault: 0) - return stripeManager.registerCard(cardFullDigits.value, month: month, year: year, securityCode: securityCode.value, postalCode: billingZip.value).doOnNext { token in + return stripeManager.registerCard(digits: cardFullDigits.value, month: month, year: year, securityCode: securityCode.value, postalCode: billingZip.value).doOnNext { token in newUser.creditCardName.value = token.card.name newUser.creditCardType.value = token.card.brand.name diff --git a/Kiosk/Bid Fulfillment/PlaceBidNetworkModel.swift b/Kiosk/Bid Fulfillment/PlaceBidNetworkModel.swift index ad002964..72776d92 100644 --- a/Kiosk/Bid Fulfillment/PlaceBidNetworkModel.swift +++ b/Kiosk/Bid Fulfillment/PlaceBidNetworkModel.swift @@ -31,13 +31,13 @@ class PlaceBidNetworkModel: NSObject, PlaceBidNetworkModelType { } fileprivate func bidOnSaleArtwork(_ saleArtwork: SaleArtwork, bidAmountCents: String, provider: AuthorizedNetworking) -> Observable { - let bidEndpoint = ArtsyAuthenticatedAPI.PlaceABid(auctionID: saleArtwork.auctionID!, artworkID: saleArtwork.artwork.id, maxBidCents: bidAmountCents) + let bidEndpoint = ArtsyAuthenticatedAPI.placeABid(auctionID: saleArtwork.auctionID!, artworkID: saleArtwork.artwork.id, maxBidCents: bidAmountCents) let request = provider .request(bidEndpoint) .filterSuccessfulStatusCodes() .mapJSON() - .mapToObject(BidderPosition) + .mapTo(object: BidderPosition.self) return request .map { position in @@ -46,7 +46,7 @@ class PlaceBidNetworkModel: NSObject, PlaceBidNetworkModelType { // We've received an error. We're going to check to see if it's type is "param_error", which indicates we were outbid. guard let error = e as? Moya.Error else { throw e } - guard case .StatusCode(let response) = error else { throw e } + guard case .statusCode(let response) = error else { throw e } let json = JSON(data: response.data) diff --git a/Kiosk/Bid Fulfillment/PlaceBidViewController.swift b/Kiosk/Bid Fulfillment/PlaceBidViewController.swift index d5579db0..0164441c 100644 --- a/Kiosk/Bid Fulfillment/PlaceBidViewController.swift +++ b/Kiosk/Bid Fulfillment/PlaceBidViewController.swift @@ -72,7 +72,7 @@ class PlaceBidViewController: UIViewController { bidDollars .map(dollarsToCurrencyString) - .bindTo(bidAmountTextField.rx_text) + .bindTo(bidAmountTextField.rx.text) .addDisposableTo(rx_disposeBag) @@ -86,25 +86,27 @@ class PlaceBidViewController: UIViewController { if let saleArtwork = nav.bidDetails.saleArtwork { let minimumNextBid = saleArtwork - .rx_observe(NSNumber.self, "minimumNextBidCents") + .rx.observe(NSNumber.self, "minimumNextBidCents") .filterNil() .map { $0 as Int } saleArtwork.viewModel .currentBidOrOpeningBidLabel() - .bindTo(currentBidTitleLabel.rx_text) + .mapToOptional() + .bindTo(currentBidTitleLabel.rx.text) .addDisposableTo(rx_disposeBag) saleArtwork.viewModel .currentBidOrOpeningBid() - .bindTo(currentBidAmountLabel.rx_text) + .mapToOptional() + .bindTo(currentBidAmountLabel.rx.text) .addDisposableTo(rx_disposeBag) minimumNextBid .map { $0 as Int } .map(toNextBidString) - .bindTo(nextBidAmountLabel.rx_text) + .bindTo(nextBidAmountLabel.rx.text) .addDisposableTo(rx_disposeBag) @@ -112,7 +114,8 @@ class PlaceBidViewController: UIViewController { .combineLatest { ints in return (ints[0]) * 100 >= (ints[1]) } - .bindTo(bidButton.rx_enabled) + .mapToOptional() + .bindTo(bidButton.rx.enabled) .addDisposableTo(rx_disposeBag) @@ -135,7 +138,7 @@ class PlaceBidViewController: UIViewController { .lotNumber() .filterNil() .takeUntil(viewWillDisappear) - .bindTo(lotNumberLabel.rx_text) + .bindTo(lotNumberLabel.rx.text) .addDisposableTo(rx_disposeBag) } @@ -159,12 +162,12 @@ class PlaceBidViewController: UIViewController { let buyersPremiumLabel = ARSerifLabel() buyersPremiumLabel.font = buyersPremiumLabel.font.withSize(16) buyersPremiumLabel.text = "This work has a " - buyersPremiumLabel.textColor = .artsyHeavyGrey() + buyersPremiumLabel.textColor = .artsyGrayBold() let buyersPremiumButton = ARUnderlineButton() buyersPremiumButton.titleLabel?.font = buyersPremiumLabel.font buyersPremiumButton.setTitle("buyers premium", for: UIControlState()) - buyersPremiumButton.setTitleColor(.artsyHeavyGrey(), for: UIControlState()) + buyersPremiumButton.setTitleColor(.artsyGrayBold(), for: UIControlState()) buyersPremiumButton.rx_action = showBuyersPremiumCommand() buyersPremiumView.addSubview(buyersPremiumLabel) @@ -183,25 +186,27 @@ class PlaceBidViewController: UIViewController { if let artist = saleArtwork.artwork.artists?.first { artist - .rx_observe(String.self, "name") + .rx.observe(String.self, "name") .filterNil() - .bindTo(artistNameLabel.rx_text) + .mapToOptional() + .bindTo(artistNameLabel.rx.text) .addDisposableTo(rx_disposeBag) } saleArtwork .artwork - .rx_observe(NSAttributedString.self, "titleAndDate") + .rx.observe(NSAttributedString.self, "titleAndDate") .takeUntil(rx.deallocated) - .bindTo(artworkTitleLabel.rx_attributedText) + .bindTo(artworkTitleLabel.rx.attributedText) .addDisposableTo(rx_disposeBag) saleArtwork .artwork - .rx_observe(String.self, "price") + .rx.observe(String.self, "price") .filterNil() + .mapToOptional() .takeUntil(rx.deallocated) - .bindTo(artworkPriceLabel.rx_text) + .bindTo(artworkPriceLabel.rx.text) .addDisposableTo(rx_disposeBag) if let url = saleArtwork.artwork.defaultImage?.thumbnailURL() { diff --git a/Kiosk/Bid Fulfillment/RegistrationMobileViewController.swift b/Kiosk/Bid Fulfillment/RegistrationMobileViewController.swift index 6a44f62d..b2b0d9df 100644 --- a/Kiosk/Bid Fulfillment/RegistrationMobileViewController.swift +++ b/Kiosk/Bid Fulfillment/RegistrationMobileViewController.swift @@ -8,7 +8,7 @@ class RegistrationMobileViewController: UIViewController, RegistrationSubControl let finished = PublishSubject() lazy var viewModel: GenericFormValidationViewModel = { - let numberIsValid = self.numberTextField.rx_text.map(isZeroLengthString).not() + let numberIsValid = self.numberTextField.rx.text.map(isZeroLength).not() return GenericFormValidationViewModel(isValid: numberIsValid, manualInvocation: self.numberTextField.rx_returnKey, finishedSubject: self.finished) }() @@ -24,7 +24,7 @@ class RegistrationMobileViewController: UIViewController, RegistrationSubControl numberTextField.text = bidDetails.newUser.phoneNumber.value numberTextField - .rx_text + .rx.text .asObservable() .mapToOptional() .takeUntil(viewWillDisappear) diff --git a/Kiosk/Bid Fulfillment/RegistrationPasswordViewController.swift b/Kiosk/Bid Fulfillment/RegistrationPasswordViewController.swift index eca0c9ed..fd8435e7 100644 --- a/Kiosk/Bid Fulfillment/RegistrationPasswordViewController.swift +++ b/Kiosk/Bid Fulfillment/RegistrationPasswordViewController.swift @@ -24,7 +24,7 @@ class RegistrationPasswordViewController: UIViewController, RegistrationSubContr return RegistrationPasswordViewModel( provider: self.provider, - password: self.passwordTextField.rx_text.asObservable(), + password: self.passwordTextField.rx.text.asObservable(), execute: self.passwordTextField.rx_returnKey, completed: self.finished, email: email) @@ -37,7 +37,7 @@ class RegistrationPasswordViewController: UIViewController, RegistrationSubContr forgotPasswordButton.isHidden = false - let passwordText = passwordTextField.rx_text + let passwordText = passwordTextField.rx.text passwordText .asObservable() .mapToOptional() @@ -83,7 +83,7 @@ class RegistrationPasswordViewController: UIViewController, RegistrationSubContr return "Create a password" } } - .bindTo(subtitleLabel.rx_text) + .bindTo(subtitleLabel.rx.text) .addDisposableTo(rx_disposeBag) passwordTextField.becomeFirstResponder() @@ -97,17 +97,17 @@ class RegistrationPasswordViewController: UIViewController, RegistrationSubContr func alertUserPasswordSent() -> Observable { return Observable.create { observer in - let alertController = UIAlertController(title: "Forgot Password", message: "We have sent you your password.", preferredStyle: .Alert) + let alertController = UIAlertController(title: "Forgot Password", message: "We have sent you your password.", preferredStyle: .alert) - let okAction = UIAlertAction(title: "OK", style: .Default) { (_) in } + let okAction = UIAlertAction(title: "OK", style: .default) { (_) in } alertController.addAction(okAction) - self.presentViewController(alertController, animated: true) { + self.present(alertController, animated: true) { observer.onCompleted() } - return NopDisposable.instance + return Disposables.create() } } diff --git a/Kiosk/Bid Fulfillment/RegistrationPostalZipViewController.swift b/Kiosk/Bid Fulfillment/RegistrationPostalZipViewController.swift index a6946cbb..b6cba2f4 100644 --- a/Kiosk/Bid Fulfillment/RegistrationPostalZipViewController.swift +++ b/Kiosk/Bid Fulfillment/RegistrationPostalZipViewController.swift @@ -6,7 +6,7 @@ class RegistrationPostalZipViewController: UIViewController, RegistrationSubCont let finished = PublishSubject() lazy var viewModel: GenericFormValidationViewModel = { - let zipCodeIsValid = self.zipCodeTextField.rx_text.map(isZeroLengthString).not() + let zipCodeIsValid = self.zipCodeTextField.rx.text.map(isZeroLength).not() return GenericFormValidationViewModel(isValid: zipCodeIsValid, manualInvocation: self.zipCodeTextField.rx_returnKey, finishedSubject: self.finished) }() @@ -23,7 +23,7 @@ class RegistrationPostalZipViewController: UIViewController, RegistrationSubCont zipCodeTextField.text = bidDetails.newUser.zipCode.value zipCodeTextField - .rx_text + .rx.text .asObservable() .mapToOptional() .takeUntil(viewWillDisappear) diff --git a/Kiosk/Bid Fulfillment/StripeManager.swift b/Kiosk/Bid Fulfillment/StripeManager.swift index 5b6e362c..49bdba55 100644 --- a/Kiosk/Bid Fulfillment/StripeManager.swift +++ b/Kiosk/Bid Fulfillment/StripeManager.swift @@ -16,19 +16,19 @@ class StripeManager: NSObject { return Observable.create { [weak self] observer in guard let me = self else { observer.onCompleted() - return NopDisposable.instance + return Disposables.create() } - me.stripeClient.createTokenWithCard(card) { (token, error) in + me.stripeClient?.createToken(with: card) { (token, error) in if (token as STPToken?).hasValue { - observer.onNext(token) + observer.onNext(token!) observer.onCompleted() } else { - observer.onError(error) + observer.onError(error!) } } - return NopDisposable.instance + return Disposables.create() } } diff --git a/Kiosk/Bid Fulfillment/SwipeCreditCardViewController.swift b/Kiosk/Bid Fulfillment/SwipeCreditCardViewController.swift index 21f20ea8..96625756 100644 --- a/Kiosk/Bid Fulfillment/SwipeCreditCardViewController.swift +++ b/Kiosk/Bid Fulfillment/SwipeCreditCardViewController.swift @@ -60,7 +60,7 @@ class SwipeCreditCardViewController: UIViewController, RegistrationSubController self.cardStatusLabel.text = "Card Status: Errored" self.setInProgress(false) self.titleLabel.text = "Please Swipe a Valid Credit Card" - self.titleLabel.textColor = .artsyRed() + self.titleLabel.textColor = .artsyRedRegular() }, onCompleted: { self.cardStatusLabel.text = "Card Status: completed" diff --git a/Kiosk/Bid Fulfillment/YourBiddingDetailsViewController.swift b/Kiosk/Bid Fulfillment/YourBiddingDetailsViewController.swift index d2f00b41..349ef1e5 100644 --- a/Kiosk/Bid Fulfillment/YourBiddingDetailsViewController.swift +++ b/Kiosk/Bid Fulfillment/YourBiddingDetailsViewController.swift @@ -2,6 +2,7 @@ import UIKit import Artsy_UILabels import Artsy_UIButtons import RxCocoa +import RxSwift class YourBiddingDetailsViewController: UIViewController { @@ -34,14 +35,14 @@ class YourBiddingDetailsViewController: UIViewController { bidDetails .paddleNumber .asObservable() - .filterNil() + .filterNilKeepOptional() .bindTo(bidderNumberLabel.rx.text) .addDisposableTo(rx_disposeBag) bidDetails .bidderPIN .asObservable() - .filterNil() + .filterNilKeepOptional() .bindTo(pinNumberLabel.rx.text) .addDisposableTo(rx_disposeBag) } diff --git a/Kiosk/Help/HelpAnimator.swift b/Kiosk/Help/HelpAnimator.swift index 7b156f5e..cd2dd1c8 100644 --- a/Kiosk/Help/HelpAnimator.swift +++ b/Kiosk/Help/HelpAnimator.swift @@ -24,10 +24,10 @@ class HelpAnimator: NSObject, UIViewControllerAnimatedTransitioning { let dismissTapGestureRecognizer = UITapGestureRecognizer() dismissTapGestureRecognizer - .rx_event + .rx.event .subscribeNext{ [weak toView] sender in - let pointInContainer = sender.locationInView(toView) - if toView?.pointInside(pointInContainer, withEvent: nil) == false { + let pointInContainer = sender.location(in: toView) + if toView?.point(inside: pointInContainer, with: nil) == false { appDelegate().helpButtonCommand().execute() } } @@ -37,7 +37,7 @@ class HelpAnimator: NSObject, UIViewControllerAnimatedTransitioning { fromView.isUserInteractionEnabled = false - containerView.backgroundColor = .black() + containerView.backgroundColor = .black containerView.addSubview(fromView) containerView.addSubview(toView) diff --git a/Kiosk/Help/HelpViewController.swift b/Kiosk/Help/HelpViewController.swift index f3c60375..f3f6ac0c 100644 --- a/Kiosk/Help/HelpViewController.swift +++ b/Kiosk/Help/HelpViewController.swift @@ -44,7 +44,7 @@ class HelpViewController: UIViewController { .appViewController .sale .value - .rx_observe(String.self, "buyersPremium") + .rx.observe(String.self, "buyersPremium") .map { $0.hasValue } }() @@ -58,7 +58,7 @@ class HelpViewController: UIViewController { super.viewDidLoad() // Configure view - view.backgroundColor = .white() + view.backgroundColor = .white addSubviews() } @@ -83,38 +83,38 @@ private extension HelpViewController { assistanceLabel.text = "Assistance" assistanceLabel.tag = SubviewTag.assistanceLabel.rawValue - let stuckLabel = titleLabel(.stuckLabel, title: "Stuck in the process?") + let stuckLabel = titleLabel(tag: .stuckLabel, title: "Stuck in the process?") - let stuckExplainLabel = wrappingSerifLabel(.stuckExplainLabel, text: "Find the nearest Artsy representative and they will assist you.") + let stuckExplainLabel = wrappingSerifLabel(tag: .stuckExplainLabel, text: "Find the nearest Artsy representative and they will assist you.") - let bidLabel = titleLabel(.bidLabel, title: "How do I place a bid?") + let bidLabel = titleLabel(tag: .bidLabel, title: "How do I place a bid?") - let bidExplainLabel = wrappingSerifLabel(.bidExplainLabel, text: "Enter the amount you would like to bid. You will confirm this bid in the next step. Enter your mobile number or bidder number and PIN that you received when you registered.") - bidExplainLabel.makeSubstringsBold(["mobile number", "bidder number", "PIN"]) + let bidExplainLabel = wrappingSerifLabel(tag: .bidExplainLabel, text: "Enter the amount you would like to bid. You will confirm this bid in the next step. Enter your mobile number or bidder number and PIN that you received when you registered.") + bidExplainLabel.makeSubstringsBold(text: ["mobile number", "bidder number", "PIN"]) - let registerButton = blackButton(.registerButton, title: "Register") + let registerButton = blackButton(tag: .registerButton, title: "Register") registerButton.rx_action = registerToBidCommand(connectedToInternetOrStubbing()) let bidderDetailsLabel = titleLabel(tag: .bidderDetailsLabel, title: "What Are Bidder Details?") - let bidderDetailsExplainLabel = wrappingSerifLabel(.bidderDetailsExplainLabel, text: "The bidder number is how you can identify yourself to bid and see your place in bid history. The PIN is a four digit number that authenticates your bid.") - bidderDetailsExplainLabel.makeSubstringsBold(["bidder number", "PIN"]) + let bidderDetailsExplainLabel = wrappingSerifLabel(tag: .bidderDetailsExplainLabel, text: "The bidder number is how you can identify yourself to bid and see your place in bid history. The PIN is a four digit number that authenticates your bid.") + bidderDetailsExplainLabel.makeSubstringsBold(text: ["bidder number", "PIN"]) - let sendDetailsButton = blackButton(.bidderDetailsButton, title: "Send me my details") + let sendDetailsButton = blackButton(tag: .bidderDetailsButton, title: "Send me my details") sendDetailsButton.rx_action = requestBidderDetailsCommand(connectedToInternetOrStubbing()) - let conditionsButton = serifButton(.conditionsOfSaleButton, title: "Conditions of Sale") + let conditionsButton = serifButton(tag: .conditionsOfSaleButton, title: "Conditions of Sale") conditionsButton.rx_action = showConditionsOfSaleCommand() - buyersPremiumButton = serifButton(.buyersPremiumButton, title: "Buyers Premium") + buyersPremiumButton = serifButton(tag: .buyersPremiumButton, title: "Buyers Premium") buyersPremiumButton.rx_action = showBuyersPremiumCommand() - let privacyButton = serifButton(.privacyPolicyButton, title: "Privacy Policy") + let privacyButton = serifButton(tag: .privacyPolicyButton, title: "Privacy Policy") privacyButton.rx_action = showPrivacyPolicyCommand() // Add subviews view.addSubview(stackView) - stackView.alignTop("0", leading: "0", bottom: nil, trailing: "0", toView: view) + stackView.alignTop("0", leading: "0", bottom: nil, trailing: "0", to: view) stackView.addSubview(assistanceLabel, withTopMargin: "\(topMargin)", sideMargin: "\(sideMargin)") stackView.addSubview(stuckLabel, withTopMargin: "\(headerMargin)", sideMargin: "\(sideMargin)") stackView.addSubview(stuckExplainLabel, withTopMargin: "\(inbetweenMargin)", sideMargin: "\(sideMargin)") @@ -149,7 +149,7 @@ private extension HelpViewController { func serifButton(tag: SubviewTag, title: String) -> ARUnderlineButton { let button = ARUnderlineButton() button.setTitle(title, for: UIControlState()) - button.setTitleColor(.artsyHeavyGrey(), for: UIControlState()) + button.setTitleColor(.artsyGrayBold(), for: UIControlState()) button.titleLabel?.font = UIFont.serifFont(withSize: 18) button.contentHorizontalAlignment = .left button.tag = tag.rawValue diff --git a/Kiosk/ListingsCollectionViewCell.swift b/Kiosk/ListingsCollectionViewCell.swift index 52826a10..c7ec59f2 100644 --- a/Kiosk/ListingsCollectionViewCell.swift +++ b/Kiosk/ListingsCollectionViewCell.swift @@ -22,22 +22,22 @@ class ListingsCollectionViewCell: UICollectionViewCell { var cancelDownloadImage: CancelDownloadImageClosure? var reuseBag: DisposeBag? - lazy var moreInfo: Observable = { - return [self.imageGestureSigal, self.infoGesture].toObservable().merge() + lazy var moreInfo: Observable = { + return Observable.from([self.imageGestureSigal, self.infoGesture]).merge() }() - fileprivate lazy var imageGestureSigal: Observable = { + fileprivate lazy var imageGestureSigal: Observable = { let recognizer = UITapGestureRecognizer() self.artworkImageView.addGestureRecognizer(recognizer) - self.artworkImageView.userInteractionEnabled = true - return recognizer.rx_event.map { _ in NSDate() } + self.artworkImageView.isUserInteractionEnabled = true + return recognizer.rx.event.map { _ in Date() } }() - fileprivate lazy var infoGesture: Observable = { + fileprivate lazy var infoGesture: Observable = { let recognizer = UITapGestureRecognizer() self.moreInfoLabel.addGestureRecognizer(recognizer) - self.moreInfoLabel.userInteractionEnabled = true - return recognizer.rx_event.map { _ in NSDate() } + self.moreInfoLabel.isUserInteractionEnabled = true + return recognizer.rx.event.map { _ in Date() } }() fileprivate var _preparingForReuse = PublishSubject() @@ -52,7 +52,7 @@ class ListingsCollectionViewCell: UICollectionViewCell { } fileprivate var _bidPressed = PublishSubject() - var bidPressed: Observable { + var bidPressed: Observable { return _bidPressed.asObservable() } @@ -89,27 +89,28 @@ class ListingsCollectionViewCell: UICollectionViewCell { // Start with things not expected to ever change. viewModel.flatMapTo(SaleArtworkViewModel.lotNumber) - .replaceNilWith("") - .bindTo(lotNumberLabel.rx_text) + .replaceNil(with: "") + .mapToOptional() + .bindTo(lotNumberLabel.rx.text) .addDisposableTo(reuseBag) viewModel.map { (viewModel) -> URL? in return viewModel.thumbnailURL }.subscribeNext { [weak self] url in guard let imageView = self?.artworkImageView else { return } - self?.downloadImage?(url: url, imageView: imageView) + self?.downloadImage?(url, imageView) }.addDisposableTo(reuseBag) viewModel.map { $0.artistName ?? "" } - .bindTo(artistNameLabel.rx_text) + .bindTo(artistNameLabel.rx.text) .addDisposableTo(reuseBag) viewModel.map { $0.titleAndDateAttributedString ?? NSAttributedString() } - .bindTo(artworkTitleLabel.rx_attributedText) + .bindTo(artworkTitleLabel.rx.attributedText) .addDisposableTo(reuseBag) viewModel.map { $0.estimateString } - .bindTo(estimateLabel.rx_text) + .bindTo(estimateLabel.rx.text) .addDisposableTo(reuseBag) // Now do properties that _do_ change. @@ -117,11 +118,13 @@ class ListingsCollectionViewCell: UICollectionViewCell { viewModel.flatMap { (viewModel) -> Observable in return viewModel.currentBid(prefix: "Current Bid: ", missingPrefix: "Starting Bid: ") } - .bindTo(currentBidLabel.rx_text) + .mapToOptional() + .bindTo(currentBidLabel.rx.text) .addDisposableTo(reuseBag) viewModel.flatMapTo(SaleArtworkViewModel.numberOfBids) - .bindTo(numberOfBidsLabel.rx_text) + .mapToOptional() + .bindTo(numberOfBidsLabel.rx.text) .addDisposableTo(reuseBag) viewModel.flatMapTo(SaleArtworkViewModel.forSale) @@ -129,10 +132,11 @@ class ListingsCollectionViewCell: UICollectionViewCell { // Button titles aren't KVO-able bidButton?.setTitle((forSale ? "BID" : "SOLD"), forState: .Normal) } - .bindTo(bidButton.rx_enabled) + .mapToOptional() + .bindTo(bidButton.rx.enabled) .addDisposableTo(reuseBag) - bidButton.rx_tap.subscribeNext { [weak self] in + bidButton.rx.tap.subscribeNext { [weak self] in self?._bidPressed.onNext(Date()) } .addDisposableTo(reuseBag) @@ -144,7 +148,7 @@ private extension ListingsCollectionViewCell { // Mark: UIView-property-methods – need an _ prefix to appease the compiler ¯\_(ツ)_/¯ class func _artworkImageView() -> UIImageView { let imageView = UIImageView() - imageView.backgroundColor = .artsyLightGrey() + imageView.backgroundColor = .artsyGrayLight() return imageView } @@ -197,7 +201,7 @@ private extension ListingsCollectionViewCell { class func _infoLabel() -> UILabel { let label = ARSansSerifLabelWithChevron() - label.tintColor = .black() + label.tintColor = .black label.text = "MORE INFO" return label } diff --git a/Kiosk/Observable+Logging.swift b/Kiosk/Observable+Logging.swift index 893500b6..7b7cb226 100644 --- a/Kiosk/Observable+Logging.swift +++ b/Kiosk/Observable+Logging.swift @@ -4,7 +4,7 @@ extension Observable { func logError(prefix: String = "Error: ") -> Observable { return self.doOn { event in switch event { - case .Error(let error): + case .error(let error): print("\(prefix)\(error)") default: break } @@ -14,7 +14,7 @@ extension Observable { func logServerError(message: String) -> Observable { return self.doOn { event in switch event { - case .Error(let e): + case .error(let e): let error = e as NSError logger.log(message) logger.log("Error: \(error.localizedDescription). \n \(error.artsyServerError())") @@ -26,7 +26,7 @@ extension Observable { func logNext() -> Observable { return self.doOn { event in switch event { - case .Next(let value): + case .next(let value): print("\(value)") default: break } diff --git a/Kiosk/Observable+Operators.swift b/Kiosk/Observable+Operators.swift index 153e4b70..dd2383ba 100644 --- a/Kiosk/Observable+Operators.swift +++ b/Kiosk/Observable+Operators.swift @@ -50,6 +50,12 @@ extension Observable where Element: OptionalType { } } + func filterNilKeepOptional() -> Observable { + return self.filter { (element) -> Bool in + return element != nil + } + } + func replaceNil(with nilValue: Element.Wrapped) -> Observable { return flatMap { (element) -> Observable in if let value = element.value { diff --git a/Kiosk/Sale Artwork Details/SaleArtworkDetailsViewController.swift b/Kiosk/Sale Artwork Details/SaleArtworkDetailsViewController.swift index 83ccb554..f1c87785 100644 --- a/Kiosk/Sale Artwork Details/SaleArtworkDetailsViewController.swift +++ b/Kiosk/Sale Artwork Details/SaleArtworkDetailsViewController.swift @@ -130,6 +130,7 @@ class SaleArtworkDetailsViewController: UIViewController { .viewModel .lotNumber() .filterNil() + .mapToOptional() .bindTo(lotNumberLabel.rx.text) .addDisposableTo(rx_disposeBag) } @@ -162,7 +163,7 @@ class SaleArtworkDetailsViewController: UIViewController { .filter { imageRights -> Bool in return imageRights.isNotEmpty }.subscribeNext { [weak self] imageRights in - let rightsLabel = label(.Serif, tag: .ImageRightsLabel) + let rightsLabel = label(.serif, tag: .imageRightsLabel) rightsLabel.text = imageRights self?.metadataStackView.addSubview(rightsLabel, withTopMargin: "22", sideMargin: "0") } @@ -190,10 +191,10 @@ class SaleArtworkDetailsViewController: UIViewController { .addDisposableTo(rx_disposeBag) let hasBids = saleArtwork - .rx_observe(NSNumber.self, "highestBidCents") + .rx.observe(NSNumber.self, "highestBidCents") .map { observeredCents -> Bool in guard let cents = observeredCents else { return false } - return (cents as Int ?? 0) > 0 + return (cents as Int) > 0 } let currentBidLabel = label(.serif, tag: .currentBidLabel) @@ -206,7 +207,8 @@ class SaleArtworkDetailsViewController: UIViewController { return .just("Starting Bid:") } } - .bindTo(currentBidLabel.rx_text) + .mapToOptional() + .bindTo(currentBidLabel.rx.text) .addDisposableTo(rx_disposeBag) metadataStackView.addSubview(currentBidLabel, withTopMargin: "22", sideMargin: "0") @@ -215,7 +217,8 @@ class SaleArtworkDetailsViewController: UIViewController { saleArtwork .viewModel .currentBid() - .bindTo(currentBidValueLabel.rx_text) + .mapToOptional() + .bindTo(currentBidValueLabel.rx.text) .addDisposableTo(rx_disposeBag) metadataStackView.addSubview(currentBidValueLabel, withTopMargin: "10", sideMargin: "0") @@ -223,18 +226,19 @@ class SaleArtworkDetailsViewController: UIViewController { saleArtwork .viewModel .numberOfBidsWithReserve - .bindTo(numberOfBidsPlacedLabel.rx_text) + .mapToOptional() + .bindTo(numberOfBidsPlacedLabel.rx.text) .addDisposableTo(rx_disposeBag) metadataStackView.addSubview(numberOfBidsPlacedLabel, withTopMargin: "10", sideMargin: "0") let bidButton = ActionButton() bidButton - .rx_tap + .rx.tap .asObservable() .subscribeNext { [weak self] _ in guard let me = self else { return } - me.bid(me.auctionID, saleArtwork: me.saleArtwork, allowAnimations: me.allowAnimations, provider: me.provider) + me.bid(auctionID: me.auctionID, saleArtwork: me.saleArtwork, allowAnimations: me.allowAnimations, provider: me.provider) } .addDisposableTo(rx_disposeBag) @@ -245,14 +249,14 @@ class SaleArtworkDetailsViewController: UIViewController { let forSale = forSale let title = forSale ? "BID" : "SOLD" - bidButton?.setTitle(title, forState: .Normal) + bidButton?.setTitle(title, for: .normal) } .addDisposableTo(rx_disposeBag) saleArtwork .viewModel .forSale() - .bindTo(bidButton.rx_enabled) + .bindTo(bidButton.rx.enabled) .addDisposableTo(rx_disposeBag) bidButton.tag = MetadataStackViewTag.bidButton.rawValue @@ -265,7 +269,7 @@ class SaleArtworkDetailsViewController: UIViewController { let buyersPremiumLabel = ARSerifLabel() buyersPremiumLabel.font = buyersPremiumLabel.font.withSize(16) buyersPremiumLabel.text = "This work has a " - buyersPremiumLabel.textColor = .artsyHeavyGrey() + buyersPremiumLabel.textColor = .artsyGrayBold() let buyersPremiumButton = ARButton() let title = "buyers premium" @@ -273,7 +277,7 @@ class SaleArtworkDetailsViewController: UIViewController { let attributedTitle = NSAttributedString(string: title, attributes: attributes) buyersPremiumButton.setTitle(title, for: UIControlState()) buyersPremiumButton.titleLabel?.attributedText = attributedTitle; - buyersPremiumButton.setTitleColor(.artsyHeavyGrey(), for: UIControlState()) + buyersPremiumButton.setTitleColor(.artsyGrayBold(), for: UIControlState()) buyersPremiumButton.rx_action = showBuyersPremiumCommand() @@ -297,15 +301,16 @@ class SaleArtworkDetailsViewController: UIViewController { let key = SDWebImageManager.shared().cacheKey(for: image.thumbnailURL() as URL!) let thumbnailImage = SDImageCache.shared().imageFromDiskCache(forKey: key) if thumbnailImage == nil { - imageView.backgroundColor = .artsyLightGrey() + imageView.backgroundColor = .artsyGrayLight() } - imageView.sd_setImage(with: image.fullsizeURL(), placeholderImage: thumbnailImage) { (image, _, _, _) in + imageView.sd_setImage(with: image.fullsizeURL(), placeholderImage: thumbnailImage, completed: { (image, _, _, _) in // If the image was successfully downloaded, make sure we aren't still displaying grey. if image != nil { imageView.backgroundColor = .clear() } - } + }) + let heightConstraintNumber = { () -> CGFloat in if let aspectRatio = image.aspectRatio { @@ -323,7 +328,7 @@ class SaleArtworkDetailsViewController: UIViewController { let recognizer = UITapGestureRecognizer() imageView.addGestureRecognizer(recognizer) recognizer - .rx_event + .rx.event .asObservable() .subscribeNext() { [weak self] _ in self?.performSegue(.ZoomIntoArtwork) @@ -369,25 +374,25 @@ class SaleArtworkDetailsViewController: UIViewController { additionalDetailScrollView.stackView.addSubview(imageView, withTopMargin: "0", sideMargin: "40") setupImageView(imageView) - let additionalInfoHeaderLabel = label(.Header) + let additionalInfoHeaderLabel = label(.header) additionalInfoHeaderLabel.text = "Additional Information" additionalDetailScrollView.stackView.addSubview(additionalInfoHeaderLabel, withTopMargin: "20", sideMargin: "40") if let blurb = saleArtwork.artwork.blurb { - let blurbLabel = label(.Body, layout: layoutSubviews) - blurbLabel.attributedText = MarkdownParser().attributedStringFromMarkdownString( blurb ) + let blurbLabel = label(.body, layout: layoutSubviews) + blurbLabel.attributedText = MarkdownParser().attributedString( fromMarkdownString: blurb ) additionalDetailScrollView.stackView.addSubview(blurbLabel, withTopMargin: "22", sideMargin: "40") } - let additionalInfoLabel = label(.Body, layout: layoutSubviews) - additionalInfoLabel.attributedText = MarkdownParser().attributedStringFromMarkdownString( saleArtwork.artwork.additionalInfo ) + let additionalInfoLabel = label(.body, layout: layoutSubviews) + additionalInfoLabel.attributedText = MarkdownParser().attributedString( fromMarkdownString: saleArtwork.artwork.additionalInfo ) additionalDetailScrollView.stackView.addSubview(additionalInfoLabel, withTopMargin: "22", sideMargin: "40") retrieveAdditionalInfo() .filter { info in return info.isNotEmpty }.subscribeNext { [weak self] info in - additionalInfoLabel.attributedText = MarkdownParser().attributedStringFromMarkdownString(info) + additionalInfoLabel.attributedText = MarkdownParser().attributedString(fromMarkdownString: info) self?.view.setNeedsLayout() self?.view.layoutIfNeeded() } @@ -401,12 +406,12 @@ class SaleArtworkDetailsViewController: UIViewController { .subscribeNext { [weak self] blurb in guard let me = self else { return } - let aboutArtistHeaderLabel = label(.Header) + let aboutArtistHeaderLabel = label(.header) aboutArtistHeaderLabel.text = "About \(artist.name)" me.additionalDetailScrollView.stackView.addSubview(aboutArtistHeaderLabel, withTopMargin: "22", sideMargin: "40") - let aboutAristLabel = label(.Body, layout: me.layoutSubviews) - aboutAristLabel.attributedText = MarkdownParser().attributedStringFromMarkdownString(blurb) + let aboutAristLabel = label(.body, layout: me.layoutSubviews) + aboutAristLabel.attributedText = MarkdownParser().attributedString(fromMarkdownString: blurb) me.additionalDetailScrollView.stackView.addSubview(aboutAristLabel, withTopMargin: "22", sideMargin: "40") } .addDisposableTo(rx_disposeBag) @@ -425,7 +430,7 @@ class SaleArtworkDetailsViewController: UIViewController { } else { return artistInfo.map{ json in - return json["image_rights"] as? String + return (json as AnyObject)["image_rights"] as? String } .filterNil() .doOnNext { imageRights in @@ -441,7 +446,7 @@ class SaleArtworkDetailsViewController: UIViewController { return .just(additionalInfo) } else { return artistInfo.map{ json in - return json["additional_information"] as? String + return (json as AnyObject)["additional_information"] as? String } .filterNil() .doOnNext{ info in @@ -458,12 +463,12 @@ class SaleArtworkDetailsViewController: UIViewController { if let blurb = artist.blurb { return .just(blurb) } else { - let retrieveArtist = provider.request(.Artist(id: artist.id)) + let retrieveArtist = provider.request(.artist(id: artist.id)) .filterSuccessfulStatusCodes() .mapJSON() return retrieveArtist.map{ json in - return json["blurb"] as? String + return (json as AnyObject)["blurb"] as? String } .filterNil() .doOnNext{ blurb in diff --git a/Kiosk/Supporting Files/PodsBridgingHeader.h b/Kiosk/Supporting Files/PodsBridgingHeader.h index fe750df6..9832a3e0 100644 --- a/Kiosk/Supporting Files/PodsBridgingHeader.h +++ b/Kiosk/Supporting Files/PodsBridgingHeader.h @@ -1,7 +1,7 @@ #import #import -#import +//#import #import #import @@ -9,7 +9,6 @@ #import -#import #import #import diff --git a/Kiosk/UIKit+Rx.swift b/Kiosk/UIKit+Rx.swift index 0e9e11ec..b732008c 100644 --- a/Kiosk/UIKit+Rx.swift +++ b/Kiosk/UIKit+Rx.swift @@ -8,12 +8,12 @@ extension UIView { MainScheduler.ensureExecutingOnScheduler() switch event { - case .Next(let value): - self?.hidden = value - case .Error(let error): + case .next(let value): + self?.isHidden = value + case .error(let error): bindingErrorToInterface(error) break - case .Completed: + case .completed: break } } @@ -22,6 +22,6 @@ extension UIView { extension UITextField { var rx_returnKey: Observable { - return rx_controlEvent(.EditingDidEndOnExit).takeUntil(rx.deallocated) + return self.rx.controlEvent(.editingDidEndOnExit).takeUntil(rx.deallocated) } } diff --git a/Kiosk/UILabel+Fonts.swift b/Kiosk/UILabel+Fonts.swift index c6916576..685c4737 100644 --- a/Kiosk/UILabel+Fonts.swift +++ b/Kiosk/UILabel+Fonts.swift @@ -2,13 +2,13 @@ import UIKit extension UILabel { func makeSubstringsBold(text: [String]) { - text.forEach { self.makeSubstringBold($0) } + text.forEach { self.makeSubstringBold(text: $0) } } func makeSubstringBold(text: String) { let attributedText = self.attributedText!.mutableCopy() as! NSMutableAttributedString - let range: NSRange! = (self.text ?? NSString() as String).range(of: text) + let range: Range! = (self.text ?? NSString() as String).range(of: text) if range.location != NSNotFound { attributedText.setAttributes([NSFontAttributeName: UIFont.serifSemiBoldFont(withSize: self.font.pointSize)], range: range) } @@ -17,13 +17,13 @@ extension UILabel { } func makeSubstringsItalic(text: [String]) { - text.forEach { self.makeSubstringItalic($0) } + text.forEach { self.makeSubstringItalic(text: $0) } } func makeSubstringItalic(text: String) { let attributedText = self.attributedText!.mutableCopy() as! NSMutableAttributedString - let range: NSRange! = (self.text ?? NSString() as String).range(of: text) + let range: Range! = (self.text ?? NSString() as String).range(of: text) if range.location != NSNotFound { attributedText.setAttributes([NSFontAttributeName: UIFont.serifItalicFont(withSize: self.font.pointSize)], range: range) } @@ -44,6 +44,6 @@ extension UILabel { func makeTransparent() { isOpaque = false - backgroundColor = .clear() + backgroundColor = .clear } } diff --git a/Kiosk/UIView+LongPressDisplayMessage.swift b/Kiosk/UIView+LongPressDisplayMessage.swift index 81ffe94e..3a795e99 100644 --- a/Kiosk/UIView+LongPressDisplayMessage.swift +++ b/Kiosk/UIView+LongPressDisplayMessage.swift @@ -16,9 +16,9 @@ extension UIView { let recognizer = UILongPressGestureRecognizer() recognizer - .rx_event + .rx.event .subscribeNext { _ in - closure(alertController: alertController(message, title: title)) + closure(alertController(message, title: title)) } .addDisposableTo(rx_disposeBag) From 7a111913c20fe5a87c68b11371aec7039494e643 Mon Sep 17 00:00:00 2001 From: Ash Furrow Date: Fri, 7 Oct 2016 12:29:46 -0400 Subject: [PATCH 07/46] [Swift 3] Cleans up remaining compiler errors. --- Kiosk/App/Models/SaleArtworkViewModel.swift | 4 ++-- Kiosk/App/NSErrorExtensions.swift | 2 +- .../Bid Fulfillment/BidCheckingNetworkModel.swift | 2 +- .../ConfirmYourBidPINViewController.swift | 4 ++-- .../ConfirmYourBidViewController.swift | 8 ++++---- Kiosk/Bid Fulfillment/PlaceBidViewController.swift | 14 +++++++------- Kiosk/ListingsCollectionViewCell.swift | 3 +-- .../SaleArtworkDetailsViewController.swift | 5 ++--- Kiosk/Sale Artwork Details/WhitespaceGobbler.swift | 2 +- Kiosk/UILabel+Fonts.swift | 10 ++++++---- 10 files changed, 27 insertions(+), 27 deletions(-) diff --git a/Kiosk/App/Models/SaleArtworkViewModel.swift b/Kiosk/App/Models/SaleArtworkViewModel.swift index 7a01875f..8117779f 100644 --- a/Kiosk/App/Models/SaleArtworkViewModel.swift +++ b/Kiosk/App/Models/SaleArtworkViewModel.swift @@ -115,7 +115,7 @@ extension SaleArtworkViewModel { func forSale() -> Observable { return saleArtwork.artwork.rx.observe(String.self, "soldStatus").filterNil().map { status in - return Artwork.SoldStatus.fromString(status) == .NotSold + return Artwork.SoldStatus.fromString(status) == .notSold } } @@ -123,7 +123,7 @@ extension SaleArtworkViewModel { func currentBid(prefix: String = "", missingPrefix: String = "") -> Observable { return saleArtwork.rx.observe(NSNumber.self, "highestBidCents").map { [weak self] highestBidCents in if let currentBidCents = highestBidCents as? Int { - return "\(prefix)\(NumberFormatter.currencyStringForCents(currentBidCents as NSNumber!))" + return "\(prefix)\(NumberFormatter.currencyString(forCents: currentBidCents as NSNumber!))" } else { return "\(missingPrefix)\(NumberFormatter.currencyString(forCents: self?.saleArtwork.openingBidCents ?? 0))" } diff --git a/Kiosk/App/NSErrorExtensions.swift b/Kiosk/App/NSErrorExtensions.swift index 1e4a9588..a532793a 100644 --- a/Kiosk/App/NSErrorExtensions.swift +++ b/Kiosk/App/NSErrorExtensions.swift @@ -9,7 +9,7 @@ extension NSError { return "\(error.message) - \(error.detail) + \(error.detail)" as NSString } else if let response = userInfo["data"] as? Response { let stringData = NSString(data: response.data, encoding: String.Encoding.utf8.rawValue) - return "Status Code: \(response.statusCode), Data Length: \(response.data.length), String Data: \(stringData)" as NSString + return "Status Code: \(response.statusCode), Data Length: \(response.data.count), String Data: \(stringData)" as NSString } return "\(userInfo)" as NSString diff --git a/Kiosk/Bid Fulfillment/BidCheckingNetworkModel.swift b/Kiosk/Bid Fulfillment/BidCheckingNetworkModel.swift index 183127dd..ee0b5899 100644 --- a/Kiosk/Bid Fulfillment/BidCheckingNetworkModel.swift +++ b/Kiosk/Bid Fulfillment/BidCheckingNetworkModel.swift @@ -60,7 +60,7 @@ class BidCheckingNetworkModel: NSObject, BidCheckingNetworkModelType { } .catchErrorJustReturn() .flatMap { _ -> Observable in - return self.checkForMaxBid(provider) + return self.checkForMaxBid(provider: provider) } } .doOnNext { _ in self.bidIsResolved.value = true diff --git a/Kiosk/Bid Fulfillment/ConfirmYourBidPINViewController.swift b/Kiosk/Bid Fulfillment/ConfirmYourBidPINViewController.swift index 575689f0..fb46c0c2 100644 --- a/Kiosk/Bid Fulfillment/ConfirmYourBidPINViewController.swift +++ b/Kiosk/Bid Fulfillment/ConfirmYourBidPINViewController.swift @@ -82,7 +82,7 @@ class ConfirmYourBidPINViewController: UIViewController { // We must check for a CC, and collect one if necessary. return me .checkForCreditCard(loggedInProvider: provider) - .doOnNext(me.gotCards) + .doOnNext(me.got) .map(void) } } @@ -140,7 +140,7 @@ class ConfirmYourBidPINViewController: UIViewController { func checkForCreditCard(loggedInProvider: AuthorizedNetworking) -> Observable<[Card]> { let endpoint = ArtsyAuthenticatedAPI.myCreditCards - return loggedInProvider.request(endpoint).filterSuccessfulStatusCodes().mapJSON().mapToObjectArray(Card.self) + return loggedInProvider.request(endpoint).filterSuccessfulStatusCodes().mapJSON().mapTo(arrayOf: Card.self) } func got(cards: [Card]) { diff --git a/Kiosk/Bid Fulfillment/ConfirmYourBidViewController.swift b/Kiosk/Bid Fulfillment/ConfirmYourBidViewController.swift index 0ecdb8c0..0ef0c3fb 100644 --- a/Kiosk/Bid Fulfillment/ConfirmYourBidViewController.swift +++ b/Kiosk/Bid Fulfillment/ConfirmYourBidViewController.swift @@ -63,7 +63,7 @@ class ConfirmYourBidViewController: UIViewController { // if so forward to PIN input VC // else send to enter email - let auctionID = nav.auctionID + let auctionID = nav.auctionID ?? "" let numberIsZeroLength = number.map(isZeroLength) @@ -85,12 +85,12 @@ class ConfirmYourBidViewController: UIViewController { var response: Moya.Response? - if case .StatusCode(let receivedResponse)? = error as? Moya.Error { + if case .statusCode(let receivedResponse)? = error as? Moya.Error { response = receivedResponse } - if let responseURL = response?.response?.URL?.absoluteString - , responseURL.containsString("v1/bidder/") { + if let responseURL = response?.response?.url?.absoluteString + , responseURL.contains("v1/bidder/") { me.performSegue(.ConfirmyourBidBidderFound) } else { diff --git a/Kiosk/Bid Fulfillment/PlaceBidViewController.swift b/Kiosk/Bid Fulfillment/PlaceBidViewController.swift index 0164441c..c81ce611 100644 --- a/Kiosk/Bid Fulfillment/PlaceBidViewController.swift +++ b/Kiosk/Bid Fulfillment/PlaceBidViewController.swift @@ -80,6 +80,9 @@ class PlaceBidViewController: UIViewController { bidDollars .map { $0 * 100 } .takeUntil(viewWillDisappear) + .map { bid in + return bid as NSNumber? + } .bindTo(nav.bidDetails.bidAmountCents) .addDisposableTo(rx_disposeBag) @@ -109,12 +112,9 @@ class PlaceBidViewController: UIViewController { .bindTo(nextBidAmountLabel.rx.text) .addDisposableTo(rx_disposeBag) - - [bidDollars, minimumNextBid] - .combineLatest { ints in + Observable.combineLatest([bidDollars, minimumNextBid], { ints in return (ints[0]) * 100 >= (ints[1]) - } - .mapToOptional() + }) .bindTo(bidButton.rx.enabled) .addDisposableTo(rx_disposeBag) @@ -136,7 +136,7 @@ class PlaceBidViewController: UIViewController { detailsStackView.addSubview(lotNumberLabel, withTopMargin: "10", sideMargin: "0") saleArtwork.viewModel .lotNumber() - .filterNil() + .filterNilKeepOptional() .takeUntil(viewWillDisappear) .bindTo(lotNumberLabel.rx.text) .addDisposableTo(rx_disposeBag) @@ -274,7 +274,7 @@ func dollarsToCurrencyString(_ dollars: Int) -> String { let formatter = NumberFormatter() formatter.locale = Locale(identifier: "en_US") formatter.numberStyle = .decimal - return formatter.string(from: NSNumber(dollars)) ?? "" + return formatter.string(from: dollars as NSNumber) ?? "" } func toNextBidString(_ cents: Int) -> String { diff --git a/Kiosk/ListingsCollectionViewCell.swift b/Kiosk/ListingsCollectionViewCell.swift index c7ec59f2..82450477 100644 --- a/Kiosk/ListingsCollectionViewCell.swift +++ b/Kiosk/ListingsCollectionViewCell.swift @@ -130,9 +130,8 @@ class ListingsCollectionViewCell: UICollectionViewCell { viewModel.flatMapTo(SaleArtworkViewModel.forSale) .doOnNext { [weak bidButton] forSale in // Button titles aren't KVO-able - bidButton?.setTitle((forSale ? "BID" : "SOLD"), forState: .Normal) + bidButton?.setTitle((forSale ? "BID" : "SOLD"), for: .normal) } - .mapToOptional() .bindTo(bidButton.rx.enabled) .addDisposableTo(reuseBag) diff --git a/Kiosk/Sale Artwork Details/SaleArtworkDetailsViewController.swift b/Kiosk/Sale Artwork Details/SaleArtworkDetailsViewController.swift index f1c87785..9f54c49f 100644 --- a/Kiosk/Sale Artwork Details/SaleArtworkDetailsViewController.swift +++ b/Kiosk/Sale Artwork Details/SaleArtworkDetailsViewController.swift @@ -304,14 +304,13 @@ class SaleArtworkDetailsViewController: UIViewController { imageView.backgroundColor = .artsyGrayLight() } - imageView.sd_setImage(with: image.fullsizeURL(), placeholderImage: thumbnailImage, completed: { (image, _, _, _) in + imageView.sd_setImage(with: image.fullsizeURL(), placeholderImage: thumbnailImage, options: [], completed: { (image, _, _, _) in // If the image was successfully downloaded, make sure we aren't still displaying grey. if image != nil { - imageView.backgroundColor = .clear() + imageView.backgroundColor = .clear } }) - let heightConstraintNumber = { () -> CGFloat in if let aspectRatio = image.aspectRatio { if aspectRatio != 0 { diff --git a/Kiosk/Sale Artwork Details/WhitespaceGobbler.swift b/Kiosk/Sale Artwork Details/WhitespaceGobbler.swift index 490d9797..b5b5a0d3 100644 --- a/Kiosk/Sale Artwork Details/WhitespaceGobbler.swift +++ b/Kiosk/Sale Artwork Details/WhitespaceGobbler.swift @@ -14,7 +14,7 @@ class WhitespaceGobbler: UIView { setContentHuggingPriority(50, for: .vertical) setContentHuggingPriority(50, for: .horizontal) - backgroundColor = .clear() + backgroundColor = .clear } override var intrinsicContentSize : CGSize { diff --git a/Kiosk/UILabel+Fonts.swift b/Kiosk/UILabel+Fonts.swift index 685c4737..b63d0dd7 100644 --- a/Kiosk/UILabel+Fonts.swift +++ b/Kiosk/UILabel+Fonts.swift @@ -7,9 +7,10 @@ extension UILabel { func makeSubstringBold(text: String) { let attributedText = self.attributedText!.mutableCopy() as! NSMutableAttributedString + let nsString = text as NSString - let range: Range! = (self.text ?? NSString() as String).range(of: text) - if range.location != NSNotFound { + // From http://stackoverflow.com/a/27041376/516359 + nsString.enumerateSubstrings(in: NSMakeRange(0, nsString.length), options: []) { (_, range, _, _) in attributedText.setAttributes([NSFontAttributeName: UIFont.serifSemiBoldFont(withSize: self.font.pointSize)], range: range) } @@ -22,9 +23,10 @@ extension UILabel { func makeSubstringItalic(text: String) { let attributedText = self.attributedText!.mutableCopy() as! NSMutableAttributedString + let nsString = text as NSString - let range: Range! = (self.text ?? NSString() as String).range(of: text) - if range.location != NSNotFound { + // From http://stackoverflow.com/a/27041376/516359 + nsString.enumerateSubstrings(in: NSMakeRange(0, nsString.length), options: []) { (_, range, _, _) in attributedText.setAttributes([NSFontAttributeName: UIFont.serifItalicFont(withSize: self.font.pointSize)], range: range) } From a673dc9f5a3b69d7ecc8afe179103f87b2b8da6c Mon Sep 17 00:00:00 2001 From: Ash Furrow Date: Fri, 7 Oct 2016 13:47:42 -0400 Subject: [PATCH 08/46] [Swift 3] Cleans up unit test compiler errors. --- Kiosk/App/Models/Artist.swift | 2 +- Kiosk/App/Models/Artwork.swift | 2 +- Kiosk/App/Models/Bid.swift | 2 +- Kiosk/App/Models/Bidder.swift | 2 +- Kiosk/App/Models/BidderPosition.swift | 2 +- Kiosk/App/Models/BuyersPremium.swift | 2 +- Kiosk/App/Models/Card.swift | 2 +- Kiosk/App/Models/GenericError.swift | 2 +- Kiosk/App/Models/Image.swift | 2 +- Kiosk/App/Models/JSONAble.swift | 2 +- Kiosk/App/Models/Location.swift | 2 +- Kiosk/App/Models/Sale.swift | 2 +- Kiosk/App/Models/SaleArtwork.swift | 2 +- Kiosk/App/Models/User.swift | 2 +- ...nfirmYourBidArtsyLoginViewController.swift | 2 +- KioskTests/App/ArtsyProviderTests.swift | 14 ++++---- KioskTests/App/LoggerTests.swift | 10 +++--- KioskTests/App/SwiftExtensionsTests.swift | 4 +-- KioskTests/AppViewControllerTests.swift | 14 ++++---- KioskTests/ArtsyAPISpec.swift | 14 ++++---- .../AdminCCBypassNetworkModelTests.swift | 22 ++++++------- ...YourBidArtsyLoginViewControllerTests.swift | 2 +- ...BidEnterYourEmailViewControllerTests.swift | 10 +++--- .../ConfirmYourBidViewControllerTests.swift | 4 +-- .../KeypadViewModelTests.swift | 8 ++--- .../LoadingViewControllerTests.swift | 4 +-- .../LoadingViewModelTests.swift | 8 ++--- .../PlaceBidNetworkModelTests.swift | 12 +++---- .../PlaceBidViewControllerTests.swift | 10 +++--- .../RegistrationPasswordViewModelTests.swift | 30 ++++++++--------- .../Bid Fulfillment/StripeManagerTests.swift | 6 ++-- KioskTests/HelpViewControllerTests.swift | 2 +- KioskTests/ListingsViewControllerTests.swift | 26 +++++++-------- KioskTests/ListingsViewModelTests.swift | 32 +++++++++---------- KioskTests/Models/BidTests.swift | 2 +- KioskTests/Models/BidderPositionTests.swift | 4 +-- KioskTests/Models/BidderTests.swift | 2 +- KioskTests/Models/ImageTests.swift | 18 +++++------ KioskTests/Models/SaleTests.swift | 17 +++++----- KioskTests/Models/SystemTimeTests.swift | 4 +-- KioskTests/RegisterFlowViewTests.swift | 12 +++---- ...aleArtworkDetailsViewControllerTests.swift | 4 +-- KioskTests/SwitchViewSpec.swift | 2 +- KioskTests/TestHelpers.swift | 22 ++++--------- KioskTests/TextFieldTests.swift | 10 +++--- KioskTests/UILabelExtensionsTests.swift | 8 ++--- KioskTests/XAppTokenSpec.swift | 12 +++---- Podfile | 3 +- Podfile.lock | 24 ++++++-------- 49 files changed, 195 insertions(+), 211 deletions(-) diff --git a/Kiosk/App/Models/Artist.swift b/Kiosk/App/Models/Artist.swift index 328c5bbb..031357dd 100644 --- a/Kiosk/App/Models/Artist.swift +++ b/Kiosk/App/Models/Artist.swift @@ -15,7 +15,7 @@ final class Artist: NSObject, JSONAbleType { self.sortableID = sortableID } - static func fromJSON(_ json:[String: AnyObject]) -> Artist { + static func fromJSON(_ json:[String: Any]) -> Artist { let json = JSON(json) let id = json["id"].stringValue diff --git a/Kiosk/App/Models/Artwork.swift b/Kiosk/App/Models/Artwork.swift index bc1e196c..9ef05c68 100644 --- a/Kiosk/App/Models/Artwork.swift +++ b/Kiosk/App/Models/Artwork.swift @@ -54,7 +54,7 @@ final class Artwork: NSObject, JSONAbleType { self.soldStatus = sold } - static func fromJSON(_ json: [String: AnyObject]) -> Artwork { + static func fromJSON(_ json: [String: Any]) -> Artwork { let json = JSON(json) let id = json["id"].stringValue diff --git a/Kiosk/App/Models/Bid.swift b/Kiosk/App/Models/Bid.swift index 2cc2ebef..594500f0 100644 --- a/Kiosk/App/Models/Bid.swift +++ b/Kiosk/App/Models/Bid.swift @@ -10,7 +10,7 @@ final class Bid: NSObject, JSONAbleType { self.amountCents = amountCents } - static func fromJSON(_ json:[String: AnyObject]) -> Bid { + static func fromJSON(_ json:[String: Any]) -> Bid { let json = JSON(json) let id = json["id"].stringValue diff --git a/Kiosk/App/Models/Bidder.swift b/Kiosk/App/Models/Bidder.swift index d01398cd..b9896e84 100644 --- a/Kiosk/App/Models/Bidder.swift +++ b/Kiosk/App/Models/Bidder.swift @@ -14,7 +14,7 @@ final class Bidder: NSObject, JSONAbleType { self.pin = pin } - static func fromJSON(_ json:[String: AnyObject]) -> Bidder { + static func fromJSON(_ json:[String: Any]) -> Bidder { let json = JSON(json) let id = json["id"].stringValue diff --git a/Kiosk/App/Models/BidderPosition.swift b/Kiosk/App/Models/BidderPosition.swift index 7db9ff43..7697a74f 100644 --- a/Kiosk/App/Models/BidderPosition.swift +++ b/Kiosk/App/Models/BidderPosition.swift @@ -14,7 +14,7 @@ final class BidderPosition: NSObject, JSONAbleType { self.processedAt = processedAt } - static func fromJSON(_ source:[String: AnyObject]) -> BidderPosition { + static func fromJSON(_ source:[String: Any]) -> BidderPosition { let json = JSON(source) let formatter = ISO8601DateFormatter() diff --git a/Kiosk/App/Models/BuyersPremium.swift b/Kiosk/App/Models/BuyersPremium.swift index f10864be..eed70610 100644 --- a/Kiosk/App/Models/BuyersPremium.swift +++ b/Kiosk/App/Models/BuyersPremium.swift @@ -10,7 +10,7 @@ final class BuyersPremium: NSObject, JSONAbleType { self.name = name } - static func fromJSON(_ json: [String: AnyObject]) -> BuyersPremium { + static func fromJSON(_ json: [String: Any]) -> BuyersPremium { let json = JSON(json) let id = json["id"].stringValue let name = json["name"].stringValue diff --git a/Kiosk/App/Models/Card.swift b/Kiosk/App/Models/Card.swift index c6cb110f..e0f5dcb4 100644 --- a/Kiosk/App/Models/Card.swift +++ b/Kiosk/App/Models/Card.swift @@ -17,7 +17,7 @@ final class Card: NSObject, JSONAbleType { self.expirationYear = expirationYear } - static func fromJSON(_ json:[String: AnyObject]) -> Card { + static func fromJSON(_ json:[String: Any]) -> Card { let json = JSON(json) let id = json["id"].stringValue diff --git a/Kiosk/App/Models/GenericError.swift b/Kiosk/App/Models/GenericError.swift index 76ed6986..5e7d0f7d 100644 --- a/Kiosk/App/Models/GenericError.swift +++ b/Kiosk/App/Models/GenericError.swift @@ -12,7 +12,7 @@ final class GenericError: NSObject, JSONAbleType { self.type = type } - static func fromJSON(_ json:[String: AnyObject]) -> GenericError { + static func fromJSON(_ json:[String: Any]) -> GenericError { let json = JSON(json) let type = json["type"].stringValue diff --git a/Kiosk/App/Models/Image.swift b/Kiosk/App/Models/Image.swift index 6261db67..7a5c3dfb 100644 --- a/Kiosk/App/Models/Image.swift +++ b/Kiosk/App/Models/Image.swift @@ -29,7 +29,7 @@ final class Image: NSObject, JSONAbleType { self.isDefault = isDefault } - static func fromJSON(_ json:[String: AnyObject]) -> Image { + static func fromJSON(_ json:[String: Any]) -> Image { let json = JSON(json) let id = json["id"].stringValue diff --git a/Kiosk/App/Models/JSONAble.swift b/Kiosk/App/Models/JSONAble.swift index cc15c62e..2d04df5d 100644 --- a/Kiosk/App/Models/JSONAble.swift +++ b/Kiosk/App/Models/JSONAble.swift @@ -1,3 +1,3 @@ protocol JSONAbleType { - static func fromJSON(_: [String: AnyObject]) -> Self + static func fromJSON(_: [String: Any]) -> Self } diff --git a/Kiosk/App/Models/Location.swift b/Kiosk/App/Models/Location.swift index 9f73f057..611eb2a3 100644 --- a/Kiosk/App/Models/Location.swift +++ b/Kiosk/App/Models/Location.swift @@ -18,7 +18,7 @@ final class Location: NSObject, JSONAbleType { self.postalCode = postalCode } - static func fromJSON(_ json: [String: AnyObject]) -> Location { + static func fromJSON(_ json: [String: Any]) -> Location { let json = JSON(json) let address = json["address"].stringValue diff --git a/Kiosk/App/Models/Sale.swift b/Kiosk/App/Models/Sale.swift index 04c4c928..a05156d6 100644 --- a/Kiosk/App/Models/Sale.swift +++ b/Kiosk/App/Models/Sale.swift @@ -22,7 +22,7 @@ final class Sale: NSObject, JSONAbleType { self.auctionState = state } - static func fromJSON(_ json:[String: AnyObject]) -> Sale { + static func fromJSON(_ json:[String: Any]) -> Sale { let json = JSON(json) let formatter = ISO8601DateFormatter() diff --git a/Kiosk/App/Models/SaleArtwork.swift b/Kiosk/App/Models/SaleArtwork.swift index 4e2ae61c..1995c809 100644 --- a/Kiosk/App/Models/SaleArtwork.swift +++ b/Kiosk/App/Models/SaleArtwork.swift @@ -52,7 +52,7 @@ final class SaleArtwork: NSObject, JSONAbleType { return SaleArtworkViewModel(saleArtwork: self) }() - static func fromJSON(_ json: [String: AnyObject]) -> SaleArtwork { + static func fromJSON(_ json: [String: Any]) -> SaleArtwork { let json = JSON(json) let id = json["id"].stringValue let artworkDict = json["artwork"].object as! [String: AnyObject] diff --git a/Kiosk/App/Models/User.swift b/Kiosk/App/Models/User.swift index 80321bb3..2e650b35 100644 --- a/Kiosk/App/Models/User.swift +++ b/Kiosk/App/Models/User.swift @@ -21,7 +21,7 @@ final class User: NSObject, JSONAbleType { self.location = location } - static func fromJSON(_ json: [String: AnyObject]) -> User { + static func fromJSON(_ json: [String: Any]) -> User { let json = JSON(json) let id = json["id"].stringValue diff --git a/Kiosk/Bid Fulfillment/ConfirmYourBidArtsyLoginViewController.swift b/Kiosk/Bid Fulfillment/ConfirmYourBidArtsyLoginViewController.swift index 6192689d..3946687a 100644 --- a/Kiosk/Bid Fulfillment/ConfirmYourBidArtsyLoginViewController.swift +++ b/Kiosk/Bid Fulfillment/ConfirmYourBidArtsyLoginViewController.swift @@ -26,7 +26,7 @@ class ConfirmYourBidArtsyLoginViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - let titleString = useArtsyBidderButton.title(for: useArtsyBidderButton.state)! ?? "" + let titleString = useArtsyBidderButton.title(for: useArtsyBidderButton.state) ?? "" let attributes = [NSUnderlineStyleAttributeName: NSUnderlineStyle.styleSingle.rawValue, NSFontAttributeName: useArtsyBidderButton.titleLabel!.font] as [String : Any]; let attrTitle = NSAttributedString(string: titleString, attributes:attributes) diff --git a/KioskTests/App/ArtsyProviderTests.swift b/KioskTests/App/ArtsyProviderTests.swift index 64fe254c..eb06a591 100644 --- a/KioskTests/App/ArtsyProviderTests.swift +++ b/KioskTests/App/ArtsyProviderTests.swift @@ -8,28 +8,28 @@ import Moya class ArtsyProviderTests: QuickSpec { override func spec() { let fakeEndpointsClosure = { (target: ArtsyAPI) -> Endpoint in - return Endpoint(URL: url(target), sampleResponseClosure: {.NetworkResponse(200, target.sampleData)}, method: target.method, parameters: target.parameters) + return Endpoint(URL: url(target), sampleResponseClosure: {.networkResponse(200, target.sampleData)}, method: target.method, parameters: target.parameters) } var fakeOnline: PublishSubject! var subject: Networking! - var defaults: NSUserDefaults! + var defaults: UserDefaults! beforeEach { fakeOnline = PublishSubject() subject = Networking(provider: OnlineProvider(endpointClosure: fakeEndpointsClosure, stubClosure: MoyaProvider.ImmediatelyStub, online: fakeOnline.asObservable())) // We fake our defaults to avoid actually hitting the network - defaults = NSUserDefaults() - defaults.setObject(NSDate.distantFuture(), forKey: "TokenExpiry") - defaults.setObject("Some key", forKey: "TokenKey") + defaults = UserDefaults() + defaults.set(NSDate.distantFuture, forKey: "TokenExpiry") + defaults.set("Some key", forKey: "TokenKey") } it ("waits for the internet to happen before continuing with network operations") { var called = false let disposeBag = DisposeBag() - subject.request(ArtsyAPI.Ping, defaults: defaults).subscribeNext { _ in + subject.request(ArtsyAPI.ping, defaults: defaults).subscribeNext { _ in called = true }.addDisposableTo(disposeBag) @@ -41,4 +41,4 @@ class ArtsyProviderTests: QuickSpec { expect(called) == true } } -} \ No newline at end of file +} diff --git a/KioskTests/App/LoggerTests.swift b/KioskTests/App/LoggerTests.swift index 6e5f4554..fce1e440 100644 --- a/KioskTests/App/LoggerTests.swift +++ b/KioskTests/App/LoggerTests.swift @@ -3,9 +3,9 @@ import Nimble @testable import Kiosk -func logPath() -> NSURL { - let docs = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).last - return docs!.URLByAppendingPathComponent("logger.txt") +func logPath() -> URL { + let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last + return docs!.appendingPathComponent("logger.txt") } class LoggerTests: QuickSpec { @@ -16,13 +16,13 @@ class LoggerTests: QuickSpec { logger.log(testString) - let fileContents = try! NSString(contentsOfURL: logPath(), encoding: NSUTF8StringEncoding) + let fileContents = try! String(contentsOf: logPath(), encoding: .utf8) expect(fileContents).to(contain(testString)) } afterEach { - try! NSFileManager.defaultManager().removeItemAtURL(logPath()) + try! FileManager.default.removeItem(at: logPath()) return } } diff --git a/KioskTests/App/SwiftExtensionsTests.swift b/KioskTests/App/SwiftExtensionsTests.swift index 6697b246..50f4818e 100644 --- a/KioskTests/App/SwiftExtensionsTests.swift +++ b/KioskTests/App/SwiftExtensionsTests.swift @@ -28,7 +28,7 @@ class AnyOccupiable: NSObject, Occupiable { class SwiftExtensionsConfiguration: QuickConfiguration { override class func configure(_ configuration: Configuration) { - sharedExamples("an Occupiable") { (sharedExampleContext: SharedExampleContext) in + sharedExamples("an Occupiable") { (sharedExampleContext: @escaping SharedExampleContext) in var empty: AnyOccupiable? var nonEmpty: AnyOccupiable? @@ -98,7 +98,7 @@ class SwiftExtensionsTests: QuickSpec { it("uses a default if no conversion is available") { let input = "not a number" - expect(input.toUIntWithDefault(4)) == 4 + expect(input.toUInt(withDefault: 4)) == 4 } } diff --git a/KioskTests/AppViewControllerTests.swift b/KioskTests/AppViewControllerTests.swift index 4d2b7592..2eba21fd 100644 --- a/KioskTests/AppViewControllerTests.swift +++ b/KioskTests/AppViewControllerTests.swift @@ -9,9 +9,9 @@ class AppViewControllerTests: QuickSpec { override func spec() { it("looks right offline") { - let subject = UIStoryboard.auction().viewControllerWithID(.NoInternetConnection) as UIViewController + let subject = UIStoryboard.auction().viewController(withID: .NoInternetConnection) as UIViewController subject.loadViewProgrammatically() - subject.view.backgroundColor = UIColor.blackColor() + subject.view.backgroundColor = UIColor.black expect(subject).to(haveValidSnapshot()) } @@ -20,7 +20,7 @@ class AppViewControllerTests: QuickSpec { var fakeReachability: Variable! beforeEach { - subject = AppViewController.instantiateFromStoryboard(auctionStoryboard) + subject = AppViewController.instantiate(from: auctionStoryboard) subject.provider = Networking.newStubbingNetworking() fakeReachability = Variable(true) @@ -31,21 +31,21 @@ class AppViewControllerTests: QuickSpec { it("shows the offlineBlockingView when offline is true"){ subject.loadViewProgrammatically() - subject.offlineBlockingView.hidden = false + subject.offlineBlockingView.isHidden = false fakeReachability.value = true - expect(subject.offlineBlockingView.hidden) == true + expect(subject.offlineBlockingView.isHidden) == true } it("hides the offlineBlockingView when offline is false"){ subject.loadViewProgrammatically() fakeReachability.value = true - expect(subject.offlineBlockingView.hidden) == true + expect(subject.offlineBlockingView.isHidden) == true fakeReachability.value = false - expect(subject.offlineBlockingView.hidden) == false + expect(subject.offlineBlockingView.isHidden) == false } } diff --git a/KioskTests/ArtsyAPISpec.swift b/KioskTests/ArtsyAPISpec.swift index 75f5114f..25924499 100644 --- a/KioskTests/ArtsyAPISpec.swift +++ b/KioskTests/ArtsyAPISpec.swift @@ -6,11 +6,11 @@ import RxSwift import Keys import Moya -func beInTheFuture() -> MatcherFunc { +func beInTheFuture() -> MatcherFunc { return MatcherFunc { actualExpression, failureMessage in let instance = try! actualExpression.evaluate()! - let now = NSDate() - return instance.compare(now) == NSComparisonResult.OrderedDescending + let now = Date() + return instance.compare(now) == ComparisonResult.orderedDescending } } @@ -22,11 +22,11 @@ class ArtsyAPISpec: QuickSpec { var networking: Networking! func newXAppRequest() -> Observable { - return networking.request(ArtsyAPI.Auctions) + return networking.request(ArtsyAPI.auctions) } beforeEach { - defaults = NSUserDefaults() + defaults = UserDefaults() networking = Networking.newStubbingNetworking() } @@ -77,14 +77,14 @@ class ArtsyAPISpec: QuickSpec { // nop }.addDisposableTo(disposeBag) - let past = NSDate(timeIntervalSinceNow: -1000) + let past = Date(timeIntervalSinceNow: -1000) expect(getDefaultsKeys(defaults).key).to(equal("STUBBED TOKEN!")) expect(getDefaultsKeys(defaults).expiry).toNot(beNil()) expect(getDefaultsKeys(defaults).expiry ?? past).to(beInTheFuture()) } it("gets XApp token if it has expired") { - let past = NSDate(timeIntervalSinceNow: -1000) + let past = Date(timeIntervalSinceNow: -1000) setDefaultsKeys(defaults, key: "some expired key", expiry: past) newXAppRequest().subscribeNext { (object) in diff --git a/KioskTests/Bid Fulfillment/AdminCCBypassNetworkModelTests.swift b/KioskTests/Bid Fulfillment/AdminCCBypassNetworkModelTests.swift index 9cd8f681..78e31a60 100644 --- a/KioskTests/Bid Fulfillment/AdminCCBypassNetworkModelTests.swift +++ b/KioskTests/Bid Fulfillment/AdminCCBypassNetworkModelTests.swift @@ -16,7 +16,7 @@ class AdminCCBypassNetworkModelTests: QuickSpec { } it("handles unregistered bidders") { - let networking = networkingForBidderCreatedByAdmin(nil) // nil indicates not registered to bid. + let networking = networkingForBidder(createdByAdmin: nil) // nil indicates not registered to bid. var receivedResult: BypassResult? waitUntil { done in @@ -30,11 +30,11 @@ class AdminCCBypassNetworkModelTests: QuickSpec { } // We need to use their providers because Nimble doesn't like comparing struct instances. - expect(receivedResult) == .RequireCC + expect(receivedResult) == .requireCC } it("handles bidders created by admins") { - let networking = networkingForBidderCreatedByAdmin(true) + let networking = networkingForBidder(createdByAdmin: true) var receivedResult: BypassResult? waitUntil { done in @@ -48,11 +48,11 @@ class AdminCCBypassNetworkModelTests: QuickSpec { } // We need to use their providers because Nimble doesn't like comparing struct instances. - expect(receivedResult) == .SkipCCRequirement + expect(receivedResult) == .skipCCRequirement } it("handles bidders not created by admins") { - let networking = networkingForBidderCreatedByAdmin(false) + let networking = networkingForBidder(createdByAdmin: false) var receivedResult: BypassResult? waitUntil { done in @@ -66,13 +66,13 @@ class AdminCCBypassNetworkModelTests: QuickSpec { } // We need to use their providers because Nimble doesn't like comparing struct instances. - expect(receivedResult) == .RequireCC + expect(receivedResult) == .requireCC } } } private func networkingForBidder(createdByAdmin: Bool?) -> AuthorizedNetworking { - let sampleData: NSData + let sampleData: Data if let createdByAdmin = createdByAdmin { let dictionary = [ @@ -80,16 +80,16 @@ private func networkingForBidder(createdByAdmin: Bool?) -> AuthorizedNetworking "saleID" : "the-best-sale-in-the-world", "created_by_admin": createdByAdmin, "ping": "1234" - ] + ] as [String : Any] - sampleData = try! NSJSONSerialization.dataWithJSONObject([dictionary], options: []) + sampleData = try! JSONSerialization.data(withJSONObject: [dictionary], options: []) } else { // nil represents no bidder, so we'll return an empty array. - sampleData = try! NSJSONSerialization.dataWithJSONObject([], options: []) + sampleData = try! JSONSerialization.data(withJSONObject: [], options: []) } let provider = OnlineProvider(endpointClosure: { target in - return Endpoint(URL: "oaishdf", sampleResponseClosure: {.NetworkResponse(200, sampleData)}) + return Endpoint(URL: "oaishdf", sampleResponseClosure: {.networkResponse(200, sampleData)}) }, stubClosure: MoyaProvider.ImmediatelyStub, online: .just(true)) diff --git a/KioskTests/Bid Fulfillment/ConfirmYourBidArtsyLoginViewControllerTests.swift b/KioskTests/Bid Fulfillment/ConfirmYourBidArtsyLoginViewControllerTests.swift index b0e44726..63a3758a 100644 --- a/KioskTests/Bid Fulfillment/ConfirmYourBidArtsyLoginViewControllerTests.swift +++ b/KioskTests/Bid Fulfillment/ConfirmYourBidArtsyLoginViewControllerTests.swift @@ -12,7 +12,7 @@ class ConfirmYourBidArtsyLoginViewControllerTests: QuickSpec { subject.loadViewProgrammatically() // Highlighting of the text field (as it becomes first responder) is inconsistent without this line. - subject.view.drawViewHierarchyInRect(CGRect.zero, afterScreenUpdates: true) + subject.view.drawHierarchy(in: CGRect.zero, afterScreenUpdates: true) expect(subject).to(haveValidSnapshot(usesDrawRect: true)) } diff --git a/KioskTests/Bid Fulfillment/ConfirmYourBidEnterYourEmailViewControllerTests.swift b/KioskTests/Bid Fulfillment/ConfirmYourBidEnterYourEmailViewControllerTests.swift index fdd9bcdc..8f6be468 100644 --- a/KioskTests/Bid Fulfillment/ConfirmYourBidEnterYourEmailViewControllerTests.swift +++ b/KioskTests/Bid Fulfillment/ConfirmYourBidEnterYourEmailViewControllerTests.swift @@ -23,7 +23,7 @@ class ConfirmYourBidEnterYourEmailViewControllerTests: QuickSpec { _ = subject.view subject.loadViewProgrammatically() // Highlighting of the text field (as it becomes first responder) is inconsistent without this line. - subject.view.drawViewHierarchyInRect(CGRect.zero, afterScreenUpdates: true) + subject.view.drawHierarchy(in: CGRect.zero, afterScreenUpdates: true) expect(subject).to(haveValidSnapshot(usesDrawRect: true)) } @@ -34,7 +34,7 @@ class ConfirmYourBidEnterYourEmailViewControllerTests: QuickSpec { subject.loadViewProgrammatically() subject.emailTextField.text = "email" - subject.emailTextField.sendActionsForControlEvents(.EditingChanged) + subject.emailTextField.sendActions(for: .editingChanged) expect(nav.bidDetails.newUser.email) == "email" } @@ -46,7 +46,7 @@ class ConfirmYourBidEnterYourEmailViewControllerTests: QuickSpec { nav.loadViewProgrammatically() subject.loadViewProgrammatically() - expect(subject.confirmButton.enabled) == false + expect(subject.confirmButton.isEnabled) == false } pending("enables the enter button when an email + password is entered") { @@ -55,9 +55,9 @@ class ConfirmYourBidEnterYourEmailViewControllerTests: QuickSpec { subject.loadViewProgrammatically() subject.emailTextField.text = "email@address.com" - subject.emailTextField.sendActionsForControlEvents(.EditingChanged) + subject.emailTextField.sendActions(for: .editingChanged) - expect(subject.confirmButton.enabled) == true + expect(subject.confirmButton.isEnabled) == true } } diff --git a/KioskTests/Bid Fulfillment/ConfirmYourBidViewControllerTests.swift b/KioskTests/Bid Fulfillment/ConfirmYourBidViewControllerTests.swift index d2a5f656..29c2baba 100644 --- a/KioskTests/Bid Fulfillment/ConfirmYourBidViewControllerTests.swift +++ b/KioskTests/Bid Fulfillment/ConfirmYourBidViewControllerTests.swift @@ -34,9 +34,9 @@ class ConfirmYourBidViewControllerTests: QuickSpec { subject.loadViewProgrammatically() - expect(subject.enterButton.enabled) == false + expect(subject.enterButton.isEnabled) == false keypadSubject.value = "3" - expect(subject.enterButton.enabled) == true + expect(subject.enterButton.isEnabled) == true } } diff --git a/KioskTests/Bid Fulfillment/KeypadViewModelTests.swift b/KioskTests/Bid Fulfillment/KeypadViewModelTests.swift index 86bfa097..afb33720 100644 --- a/KioskTests/Bid Fulfillment/KeypadViewModelTests.swift +++ b/KioskTests/Bid Fulfillment/KeypadViewModelTests.swift @@ -41,7 +41,7 @@ class KeypadViewModelTests: QuickSpec { it("adds digits") { waitUntil { done in [1,3,3,7] - .reduce(Observable.empty(), combine: { (observable, input) -> Observable in + .reduce(Observable.empty(), { (observable, input) -> Observable in observable.then { subject.addDigitAction.execute(input) } }) .subscribeCompleted { @@ -59,7 +59,7 @@ class KeypadViewModelTests: QuickSpec { waitUntil { done in [1,3,3,3,3,3,3,3,3,3,3,3,7] - .reduce(Observable.empty(), combine: { (observable, input) -> Observable in + .reduce(Observable.empty(), { (observable, input) -> Observable in observable.then { subject.addDigitAction.execute(input) } }) .subscribeCompleted { @@ -75,7 +75,7 @@ class KeypadViewModelTests: QuickSpec { it("handles prepended zeros") { waitUntil { done in [0,1,3,3,7] - .reduce(Observable.empty(), combine: { (observable, input) -> Observable in + .reduce(Observable.empty(), { (observable, input) -> Observable in observable.then { subject.addDigitAction.execute(input) } }) .subscribeCompleted { @@ -110,7 +110,7 @@ class KeypadViewModelTests: QuickSpec { it("deletes") { waitUntil { done in [1,3,3] - .reduce(Observable.empty(), combine: { (observable, input) -> Observable in + .reduce(Observable.empty(), { (observable, input) -> Observable in observable.then { subject.addDigitAction.execute(input) } }) .then { diff --git a/KioskTests/Bid Fulfillment/LoadingViewControllerTests.swift b/KioskTests/Bid Fulfillment/LoadingViewControllerTests.swift index 0faa8b22..5237e3b2 100644 --- a/KioskTests/Bid Fulfillment/LoadingViewControllerTests.swift +++ b/KioskTests/Bid Fulfillment/LoadingViewControllerTests.swift @@ -160,7 +160,7 @@ class LoadingViewControllerTests: QuickSpec { let loadingViewControllerTestImage = UIImage.testImage(named: "artwork", ofType: "jpg") func testLoadingViewController() -> LoadingViewController { - let controller = UIStoryboard.fulfillment().viewControllerWithID(.LoadingBidsorRegistering).wrapInFulfillmentNav() as! LoadingViewController + let controller = UIStoryboard.fulfillment().viewController(withID: .LoadingBidsorRegistering).wrapInFulfillmentNav() as! LoadingViewController return controller } @@ -183,7 +183,7 @@ class StubLoadingViewModel: LoadingViewModelType { func performActions() -> Observable { if completes { if errors { - return Observable.error(NSError(domain: "", code: 0, userInfo: nil) as ErrorType) + return Observable.error(NSError(domain: "", code: 0, userInfo: nil) as Swift.Error) } else { return Observable.empty() } diff --git a/KioskTests/Bid Fulfillment/LoadingViewModelTests.swift b/KioskTests/Bid Fulfillment/LoadingViewModelTests.swift index b1c37a37..2fa29d6a 100644 --- a/KioskTests/Bid Fulfillment/LoadingViewModelTests.swift +++ b/KioskTests/Bid Fulfillment/LoadingViewModelTests.swift @@ -80,7 +80,7 @@ class LoadingViewModelTests: QuickSpec { it("creates a new bidder if necessary") { subject = LoadingViewModel(provider: Networking.newStubbingNetworking(), bidNetworkModel: stubbedNetworkModel, placingBid: false, actionsComplete: Observable.never()) - kioskWaitUntil { (done) in + waitUntil { (done) in subject.performActions().subscribeCompleted { done() }.addDisposableTo(disposeBag) } @@ -102,7 +102,7 @@ class LoadingViewModelTests: QuickSpec { } it("places a bid if necessary") { - kioskWaitUntil { done in + waitUntil { done in subject.performActions().subscribeCompleted { done() }.addDisposableTo(disposeBag) return } @@ -111,7 +111,7 @@ class LoadingViewModelTests: QuickSpec { } it("waits for bid resolution if bid was placed") { - kioskWaitUntil { done in + waitUntil { done in subject.performActions().subscribeCompleted { done() }.addDisposableTo(disposeBag) } @@ -141,7 +141,7 @@ class StubPlaceBidNetworkModel: PlaceBidNetworkModelType { var bidDetails: BidDetails = testBidDetails() - func bid(provider: AuthorizedNetworking) -> Observable { + func bid(_ provider: AuthorizedNetworking) -> Observable { hasBid = true return Observable.just(bidderID) diff --git a/KioskTests/Bid Fulfillment/PlaceBidNetworkModelTests.swift b/KioskTests/Bid Fulfillment/PlaceBidNetworkModelTests.swift index d20d43cf..397a8ee3 100644 --- a/KioskTests/Bid Fulfillment/PlaceBidNetworkModelTests.swift +++ b/KioskTests/Bid Fulfillment/PlaceBidNetworkModelTests.swift @@ -57,14 +57,14 @@ class PlaceBidNetworkModelTests: QuickSpec { var bidCents: String? let provider = OnlineProvider(endpointClosure: { target -> (Endpoint) in - if case .PlaceABid(let receivedAuctionID, let receivedArtworkID, let receivedBidCents) = target { + if case .placeABid(let receivedAuctionID, let receivedArtworkID, let receivedBidCents) = target { auctionID = receivedAuctionID artworkID = receivedArtworkID bidCents = receivedBidCents } - let url = target.baseURL.URLByAppendingPathComponent(target.path).absoluteString - return Endpoint(URL: url, sampleResponseClosure: {.NetworkResponse(200, stubbedResponse("CreateABid"))}, method: target.method, parameters: target.parameters) + let url = target.baseURL.appendingPathComponent(target.path).absoluteString + return Endpoint(URL: url, sampleResponseClosure: {.networkResponse(200, stubbedResponse("CreateABid"))}, method: target.method, parameters: target.parameters) }, stubClosure: MoyaProvider.ImmediatelyStub, online: Observable.just(true)) @@ -88,8 +88,8 @@ class PlaceBidNetworkModelTests: QuickSpec { beforeEach { let provider = OnlineProvider(endpointClosure: { target -> (Endpoint) in - let url = target.baseURL.URLByAppendingPathComponent(target.path).absoluteString - return Endpoint(URL: url, sampleResponseClosure: {.NetworkResponse(400, stubbedResponse("CreateABidFail"))}, method: target.method, parameters: target.parameters) + let url = target.baseURL.appendingPathComponent(target.path).absoluteString + return Endpoint(URL: url, sampleResponseClosure: {.networkResponse(400, stubbedResponse("CreateABidFail"))}, method: target.method, parameters: target.parameters) }, stubClosure: MoyaProvider.ImmediatelyStub, online: Observable.just(true)) networking = AuthorizedNetworking(provider: provider) @@ -126,4 +126,4 @@ class PlaceBidNetworkModelTests: QuickSpec { } } } -} \ No newline at end of file +} diff --git a/KioskTests/Bid Fulfillment/PlaceBidViewControllerTests.swift b/KioskTests/Bid Fulfillment/PlaceBidViewControllerTests.swift index 59628ff1..9a104935 100644 --- a/KioskTests/Bid Fulfillment/PlaceBidViewControllerTests.swift +++ b/KioskTests/Bid Fulfillment/PlaceBidViewControllerTests.swift @@ -6,8 +6,8 @@ import Kiosk import Nimble_Snapshots class PlaceBidViewControllerConfiguration: QuickConfiguration { - override class func configure(configuration: Configuration) { - sharedExamples("a bid view controller view controller") { (sharedExampleContext: SharedExampleContext) in + override class func configure(_ configuration: Configuration) { + sharedExamples("a bid view controller view controller") { (sharedExampleContext: @escaping SharedExampleContext) in var subject: PlaceBidViewController! var nav: FulfillmentNavigationController! @@ -43,7 +43,7 @@ class PlaceBidViewControllerConfiguration: QuickConfiguration { class PlaceBidViewControllerTests: QuickSpec { override func spec() { var subject: PlaceBidViewController! - let artworkJSON: [String: AnyObject] = [ + let artworkJSON: [String: Any] = [ "id":"artwork_id" as AnyObject, "title" : "The Artwork Title" as AnyObject, "date": "23rd Nov" as AnyObject, @@ -167,10 +167,10 @@ class PlaceBidViewControllerTests: QuickSpec { nav.loadViewProgrammatically() subject.loadViewProgrammatically() - expect(subject.bidButton.enabled) == false + expect(subject.bidButton.isEnabled) == false customKeySubject.onNext(200) - expect(subject.bidButton.enabled) == true + expect(subject.bidButton.isEnabled) == true } it("passes the bid amount to the nav controller") { diff --git a/KioskTests/Bid Fulfillment/RegistrationPasswordViewModelTests.swift b/KioskTests/Bid Fulfillment/RegistrationPasswordViewModelTests.swift index 494d5656..4a6d71fd 100644 --- a/KioskTests/Bid Fulfillment/RegistrationPasswordViewModelTests.swift +++ b/KioskTests/Bid Fulfillment/RegistrationPasswordViewModelTests.swift @@ -19,27 +19,27 @@ class RegistrationPasswordViewModelTests: QuickSpec { let endpointsClosure = { (target: ArtsyAPI) -> Endpoint in switch target { - case ArtsyAPI.FindExistingEmailRegistration(let email): + case ArtsyAPI.findExistingEmailRegistration(let email): emailCheck?() expect(email) == testEmail - return Endpoint(URL: url(target), sampleResponseClosure: {.NetworkResponse(emailExists ? 200 : 404, NSData())}, method: target.method, parameters: target.parameters) - case ArtsyAPI.LostPasswordNotification(let email): + return Endpoint(URL: url(target), sampleResponseClosure: {.networkResponse(emailExists ? 200 : 404, Data())}, method: target.method, parameters: target.parameters) + case ArtsyAPI.lostPasswordNotification(let email): passwordCheck?() expect(email) == testEmail - return Endpoint(URL: url(target), sampleResponseClosure: {.NetworkResponse(passwordRequestSucceeds ? 200 : 404, NSData())}, method: target.method, parameters: target.parameters) - case ArtsyAPI.XAuth(let email, let password): + return Endpoint(URL: url(target), sampleResponseClosure: {.networkResponse(passwordRequestSucceeds ? 200 : 404, Data())}, method: target.method, parameters: target.parameters) + case ArtsyAPI.xAuth(let email, let password): loginCheck?() expect(email) == testEmail expect(password) == testPassword // Fail auth (wrong password maybe) - return Endpoint(URL: url(target), sampleResponseClosure: {.NetworkResponse(loginSucceeds ? 200 : 403, NSData())}, method: target.method, parameters: target.parameters) - case .XApp: + return Endpoint(URL: url(target), sampleResponseClosure: {.networkResponse(loginSucceeds ? 200 : 403, Data())}, method: target.method, parameters: target.parameters) + case .xApp: // Any XApp requests are incidental; ignore. return MoyaProvider.DefaultEndpointMapping(target) default: // Fail on all other cases fail("Unexpected network call") - return Endpoint(URL: url(target), sampleResponseClosure: {.NetworkResponse(200, NSData())}, method: target.method, parameters: target.parameters) + return Endpoint(URL: url(target), sampleResponseClosure: {.networkResponse(200, Data())}, method: target.method, parameters: target.parameters) } } @@ -89,7 +89,7 @@ class RegistrationPasswordViewModelTests: QuickSpec { checked = true }, loginSucceeds: true, loginCheck: nil, passwordRequestSucceeds: true, passwordCheck: nil) - let subject = self.testSubject(networking) + let subject = self.testSubject(provider: networking) let disposeBag = DisposeBag() waitUntil { done in @@ -108,7 +108,7 @@ class RegistrationPasswordViewModelTests: QuickSpec { exists = true }, loginSucceeds: true, loginCheck: nil, passwordRequestSucceeds: true, passwordCheck: nil) - let subject = self.testSubject(networking) + let subject = self.testSubject(provider: networking) let disposeBag = DisposeBag() subject @@ -134,7 +134,7 @@ class RegistrationPasswordViewModelTests: QuickSpec { exists = true }, loginSucceeds: true, loginCheck: nil, passwordRequestSucceeds: true, passwordCheck: nil) - let subject = self.testSubject(networking) + let subject = self.testSubject(provider: networking) let disposeBag = DisposeBag() subject @@ -164,7 +164,7 @@ class RegistrationPasswordViewModelTests: QuickSpec { authed = true }, passwordRequestSucceeds: true, passwordCheck: nil) - let subject = self.testSubject(networking) + let subject = self.testSubject(provider: networking) let disposeBag = DisposeBag() @@ -181,7 +181,7 @@ class RegistrationPasswordViewModelTests: QuickSpec { it("sends an error on the command if the authorization fails") { let networking = self.stubProvider(emailExists: true, emailCheck: nil, loginSucceeds: false, loginCheck: nil, passwordRequestSucceeds: true, passwordCheck: nil) - let subject = self.testSubject(networking) + let subject = self.testSubject(provider: networking) let disposeBag = DisposeBag() var errored = false @@ -208,7 +208,7 @@ class RegistrationPasswordViewModelTests: QuickSpec { let invocation = PublishSubject() - let subject = self.testSubject(networking, invocation: invocation) + let subject = self.testSubject(provider: networking, invocation: invocation) let disposeBag = DisposeBag() var completed = false @@ -258,7 +258,7 @@ class RegistrationPasswordViewModelTests: QuickSpec { sent = true }) - let subject = self.testSubject(networking) + let subject = self.testSubject(provider: networking) let disposeBag = DisposeBag() waitUntil { done in diff --git a/KioskTests/Bid Fulfillment/StripeManagerTests.swift b/KioskTests/Bid Fulfillment/StripeManagerTests.swift index 62c5be78..5037411e 100644 --- a/KioskTests/Bid Fulfillment/StripeManagerTests.swift +++ b/KioskTests/Bid Fulfillment/StripeManagerTests.swift @@ -26,7 +26,7 @@ class StripeManagerTests: QuickSpec { it("sends the correct token upon success") { waitUntil { done in - subject.registerCard("", month: 0, year: 0, securityCode: "", postalCode: "").subscribeNext { (object) in + subject.registerCard(digits: "", month: 0, year: 0, securityCode: "", postalCode: "").subscribeNext { (object) in let token = object expect(token.tokenId) == "12345" @@ -38,7 +38,7 @@ class StripeManagerTests: QuickSpec { it("sends the correct token upon success") { var completed = false waitUntil { done in - subject.registerCard("", month: 0, year: 0, securityCode: "", postalCode: "").subscribeCompleted { + subject.registerCard(digits: "", month: 0, year: 0, securityCode: "", postalCode: "").subscribeCompleted { completed = true done() }.addDisposableTo(disposeBag) @@ -52,7 +52,7 @@ class StripeManagerTests: QuickSpec { var errored = false waitUntil { done in - subject.registerCard("", month: 0, year: 0, securityCode: "", postalCode: "").subscribeError { _ in + subject.registerCard(digits: "", month: 0, year: 0, securityCode: "", postalCode: "").subscribeError { _ in errored = true done() }.addDisposableTo(disposeBag) diff --git a/KioskTests/HelpViewControllerTests.swift b/KioskTests/HelpViewControllerTests.swift index 5adb8d7d..d6db6df4 100644 --- a/KioskTests/HelpViewControllerTests.swift +++ b/KioskTests/HelpViewControllerTests.swift @@ -7,7 +7,7 @@ import Kiosk class HelpViewControllerConfiguration: QuickConfiguration { override class func configure(_ configuration: Configuration) { - sharedExamples("a help view controller") { (sharedExampleContext: SharedExampleContext) in + sharedExamples("a help view controller") { (sharedExampleContext: @escaping SharedExampleContext) in var subject: HelpViewController! beforeEach { diff --git a/KioskTests/ListingsViewControllerTests.swift b/KioskTests/ListingsViewControllerTests.swift index 0a95b084..5cd15e72 100644 --- a/KioskTests/ListingsViewControllerTests.swift +++ b/KioskTests/ListingsViewControllerTests.swift @@ -9,7 +9,7 @@ import Moya class ListingsViewControllerConfiguration: QuickConfiguration { override class func configure(_ configuration: Configuration) { - sharedExamples("a listings controller") { (sharedExampleContext: SharedExampleContext) in + sharedExamples("a listings controller") { (sharedExampleContext: @escaping SharedExampleContext) in var subject: ListingsViewController! var viewModel: ListingsViewControllerTestsStubbedViewModel! @@ -20,36 +20,36 @@ class ListingsViewControllerConfiguration: QuickConfiguration { } it("grid") { - subject.switchView[0]?.sendActionsForControlEvents(.TouchUpInside) + subject.switchView[0]?.sendActions(for: .touchUpInside) expect(subject).to( haveValidSnapshot(usesDrawRect: true) ) } it("least bids") { - subject.switchView[1]?.sendActionsForControlEvents(.TouchUpInside) + subject.switchView[1]?.sendActions(for: .touchUpInside) viewModel._gridSelected.value = false expect(subject).to( haveValidSnapshot(usesDrawRect: true) ) } it("most bids") { - subject.switchView[2]?.sendActionsForControlEvents(.TouchUpInside) + subject.switchView[2]?.sendActions(for: .touchUpInside) viewModel._gridSelected.value = false expect(subject).to( haveValidSnapshot(usesDrawRect: true) ) } it("highest bid") { - subject.switchView[3]?.sendActionsForControlEvents(.TouchUpInside) + subject.switchView[3]?.sendActions(for: .touchUpInside) viewModel._gridSelected.value = false expect(subject).to( haveValidSnapshot(usesDrawRect: true) ) } it("lowest bid") { - subject.switchView[4]?.sendActionsForControlEvents(.TouchUpInside) + subject.switchView[4]?.sendActions(for: .touchUpInside) viewModel._gridSelected.value = false expect(subject).to( haveValidSnapshot(usesDrawRect: true) ) } it("alphabetical") { - subject.switchView[5]?.sendActionsForControlEvents(.TouchUpInside) + subject.switchView[5]?.sendActions(for: .touchUpInside) viewModel._gridSelected.value = false expect(subject).to( haveValidSnapshot(usesDrawRect: true) ) } @@ -131,23 +131,23 @@ class ListingsViewControllerTestsStubbedViewModel: NSObject, ListingsViewModelTy var scheduleOnBackground: (_ observable: Observable) -> Observable = { observable in observable } var scheduleOnForeground: (_ observable: Observable<[SaleArtwork]>) -> Observable<[SaleArtwork]> = { observable in observable } - func saleArtworkViewModelAtIndexPath(_ indexPath: NSIndexPath) -> SaleArtworkViewModel { + func saleArtworkViewModel(atIndexPath indexPath: IndexPath) -> SaleArtworkViewModel { let saleArtwork = testSaleArtwork() - saleArtwork.lotNumber = lotNumber + saleArtwork.lotNumber = lotNumber as NSNumber? if let soldStatus = soldStatus { saleArtwork.artwork.soldStatus = soldStatus } - saleArtwork.openingBidCents = 1_000_00 * (indexPath.item + 1) + saleArtwork.openingBidCents = (1_000_00 * (indexPath.item + 1)) as NSNumber return saleArtwork.viewModel } - func showDetailsForSaleArtworkAtIndexPath(_ indexPath: IndexPath) { } + func showDetailsForSaleArtwork(atIndexPath indexPath: IndexPath) { } - func presentModalForSaleArtworkAtIndexPath(_ indexPath: IndexPath) { } - func imageAspectRatioForSaleArtworkAtIndexPath(_ indexPath: IndexPath) -> CGFloat? { return nil } + func presentModalForSaleArtwork(atIndexPath indexPath: IndexPath) { } + func imageAspectRatioForSaleArtwork(atIndexPath indexPath: IndexPath) -> CGFloat? { return nil } // Testing values var lotNumber: Int? diff --git a/KioskTests/ListingsViewModelTests.swift b/KioskTests/ListingsViewModelTests.swift index 73066aea..593cf6a5 100644 --- a/KioskTests/ListingsViewModelTests.swift +++ b/KioskTests/ListingsViewModelTests.swift @@ -28,14 +28,14 @@ class ListingsViewModelTests: QuickSpec { let endpointsClosure: MoyaProvider.EndpointClosure = { (target: ArtsyAPI) -> Endpoint in switch target { - case ArtsyAPI.AuctionListings: + case ArtsyAPI.auctionListings: if let page = target.parameters!["page"] as? Int { - return Endpoint(URL: url(target), sampleResponseClosure: {.NetworkResponse(200, listingsDataForPage(page, bidCount: bidCount, modelCount: saleArtworksCount))}, method: target.method, parameters: target.parameters) + return Endpoint(URL: url(target), sampleResponseClosure: {.networkResponse(200, listingsData(forPage: page, bidCount: bidCount, modelCount: saleArtworksCount))}, method: target.method, parameters: target.parameters) } else { - return Endpoint(URL: url(target), sampleResponseClosure: {.NetworkResponse(200, target.sampleData)}, method: target.method, parameters: target.parameters) + return Endpoint(URL: url(target), sampleResponseClosure: {.networkResponse(200, target.sampleData)}, method: target.method, parameters: target.parameters) } default: - return Endpoint(URL: url(target), sampleResponseClosure: {.NetworkResponse(200, target.sampleData)}, method: target.method, parameters: target.parameters) + return Endpoint(URL: url(target), sampleResponseClosure: {.networkResponse(200, target.sampleData)}, method: target.method, parameters: target.parameters) } } @@ -54,7 +54,7 @@ class ListingsViewModelTests: QuickSpec { subject = ListingsViewModel(provider: provider, selectedIndex: Observable.just(0), showDetails: { _ in }, presentModal: { _ in }, pageSize: 2, logSync: { _ in}, scheduleOnBackground: testScheduleOnBackground, scheduleOnForeground: testScheduleOnForeground) - kioskWaitUntil { done in + waitUntil { done in subject.updatedContents.take(1).subscribeCompleted { done() }.addDisposableTo(disposeBag) @@ -69,7 +69,7 @@ class ListingsViewModelTests: QuickSpec { // Verify that initial value is correct waitUntil(timeout: 5) { done in subject.updatedContents.take(1).flatMap { _ in - return subject.saleArtworkViewModelAtIndexPath(NSIndexPath(forItem: 0, inSection: 0)).numberOfBids().take(1) + return subject.saleArtworkViewModel(atIndexPath: IndexPath(item: 0, section: 0)).numberOfBids().take(1) }.subscribeNext { string in expect(string) == "\(initialBidCount) bids placed" done() @@ -81,7 +81,7 @@ class ListingsViewModelTests: QuickSpec { waitUntil(timeout: 5) { done in // We skip 1 to avoid getting the existing value, and wait for the updated one when the subject syncs. - subject.saleArtworkViewModelAtIndexPath(NSIndexPath(forItem: 0, inSection: 0)).numberOfBids().skip(1).subscribeNext { string in + subject.saleArtworkViewModel(atIndexPath: IndexPath(item: 0, section: 0)).numberOfBids().skip(1).subscribeNext { string in expect(string) == "\(finalBidCount) bids placed" done() }.addDisposableTo(disposeBag) @@ -118,10 +118,10 @@ class ListingsViewModelTests: QuickSpec { let endpointsClosure: MoyaProvider.EndpointClosure = { (target: ArtsyAPI) -> Endpoint in switch target { - case ArtsyAPI.AuctionListings: - return Endpoint(URL: url(target), sampleResponseClosure: {.NetworkResponse(200, listingsDataForPage(1, bidCount: 0, modelCount: 3, reverseIDs: reverseIDs))}, method: target.method, parameters: target.parameters) + case ArtsyAPI.auctionListings: + return Endpoint(URL: url(target), sampleResponseClosure: {.networkResponse(200, listingsData(forPage: 1, bidCount: 0, modelCount: 3, reverseIDs: reverseIDs))}, method: target.method, parameters: target.parameters) default: - return Endpoint(URL: url(target), sampleResponseClosure: {.NetworkResponse(200, target.sampleData)}, method: target.method, parameters: target.parameters) + return Endpoint(URL: url(target), sampleResponseClosure: {.networkResponse(200, target.sampleData)}, method: target.method, parameters: target.parameters) } } @@ -133,18 +133,18 @@ class ListingsViewModelTests: QuickSpec { var subsequentFirstLotID: String? // First we get our initial sync - kioskWaitUntil { done in + waitUntil { done in subject.updatedContents.take(1).subscribeCompleted { - initialFirstLotID = subject.saleArtworkViewModelAtIndexPath(NSIndexPath(forItem: 0, inSection: 0)).saleArtworkID + initialFirstLotID = subject.saleArtworkViewModel(atIndexPath: IndexPath(item: 0, section: 0)).saleArtworkID done() }.addDisposableTo(disposeBag) } // Now we reverse the lot numbers reverseIDs = true - kioskWaitUntil { done in + waitUntil { done in subject.updatedContents.skip(1).take(1).subscribeCompleted { - subsequentFirstLotID = subject.saleArtworkViewModelAtIndexPath(NSIndexPath(forItem: 0, inSection: 0)).saleArtworkID + subsequentFirstLotID = subject.saleArtworkViewModel(atIndexPath: IndexPath(item: 0, section: 0)).saleArtworkID done() }.addDisposableTo(disposeBag) } @@ -159,7 +159,7 @@ class ListingsViewModelTests: QuickSpec { } -func listingsData(forPage page: Int, bidCount: Int, modelCount: Int?, reverseIDs: Bool = false) -> NSData { +func listingsData(forPage page: Int, bidCount: Int, modelCount: Int?, reverseIDs: Bool = false) -> Data { let count = modelCount ?? (page == 1 ? 2 : 1) let models = Array(1...count).map { index -> NSDictionary in @@ -177,5 +177,5 @@ func listingsData(forPage page: Int, bidCount: Int, modelCount: Int?, reverseIDs ] } - return try! NSJSONSerialization.dataWithJSONObject(models, options: []) + return try! JSONSerialization.data(withJSONObject: models, options: []) } diff --git a/KioskTests/Models/BidTests.swift b/KioskTests/Models/BidTests.swift index 8c0b4a3c..c45019c5 100644 --- a/KioskTests/Models/BidTests.swift +++ b/KioskTests/Models/BidTests.swift @@ -8,7 +8,7 @@ class BidTests: QuickSpec { it("converts from JSON") { let id = "saf32sadasd" let amount = 100000 - let data:[String: AnyObject] = ["id":id , "amount_cents" : amount ] + let data:[String: Any] = ["id":id as AnyObject , "amount_cents" : amount ] let bid = Bid.fromJSON(data) diff --git a/KioskTests/Models/BidderPositionTests.swift b/KioskTests/Models/BidderPositionTests.swift index 8e6a6733..791bda69 100644 --- a/KioskTests/Models/BidderPositionTests.swift +++ b/KioskTests/Models/BidderPositionTests.swift @@ -12,9 +12,9 @@ class BidderPositionTests: QuickSpec { let bidID = "saf32sadasd" let bidAmount = 100000 - let bidData:[String: AnyObject] = ["id": bidID, "amount_cents" : bidAmount ] + let bidData:[String: Any] = ["id": bidID as AnyObject, "amount_cents" : bidAmount ] - let data:[String: AnyObject] = ["id":id , "max_bid_amount_cents" : maxBidAmountCents, "highest_bid":bidData] + let data:[String: Any] = ["id":id as AnyObject , "max_bid_amount_cents" : maxBidAmountCents, "highest_bid":bidData] let position = BidderPosition.fromJSON(data) diff --git a/KioskTests/Models/BidderTests.swift b/KioskTests/Models/BidderTests.swift index 04f98555..acc5da66 100644 --- a/KioskTests/Models/BidderTests.swift +++ b/KioskTests/Models/BidderTests.swift @@ -9,7 +9,7 @@ class BidderTests: QuickSpec { it("converts from JSON") { let id = "324ddf445" let saleID = "asdkhaskda" - let data:[String: AnyObject] = ["id":id , "sale" : ["id": saleID]] + let data:[String: Any] = ["id":id , "sale" : ["id": saleID]] let bidder = Bidder.fromJSON(data) diff --git a/KioskTests/Models/ImageTests.swift b/KioskTests/Models/ImageTests.swift index ef9d391f..77dcfa33 100644 --- a/KioskTests/Models/ImageTests.swift +++ b/KioskTests/Models/ImageTests.swift @@ -13,7 +13,7 @@ class ImageTests: QuickSpec { it("converts from JSON") { let imageFormats = ["big", "small", "patch"] - let data:[String: AnyObject] = [ "id": id, "image_url": url, "image_versions": imageFormats, "original_width": size.width, "original_height": size.height] + let data:[String: Any] = [ "id": id as AnyObject, "image_url": url, "image_versions": imageFormats, "original_width": size.width, "original_height": size.height] let image = Image.fromJSON(data) @@ -24,23 +24,23 @@ class ImageTests: QuickSpec { } it("generates a thumbnail url") { - var image = self.imageForVersion("large") - expect(image.thumbnailURL()).to(beAnInstanceOf(NSURL)) + var image = self.image(forVersion: "large") + expect(image.thumbnailURL()).to(beAnInstanceOf(NSURL.self)) - image = self.imageForVersion("medium") - expect(image.thumbnailURL()).to(beAnInstanceOf(NSURL)) + image = self.image(forVersion: "medium") + expect(image.thumbnailURL()).to(beAnInstanceOf(NSURL.self)) - image = self.imageForVersion("larger") - expect(image.thumbnailURL()).to(beAnInstanceOf(NSURL)) + image = self.image(forVersion: "larger") + expect(image.thumbnailURL()).to(beAnInstanceOf(NSURL.self)) } it("handles unknown image formats"){ - let image = self.imageForVersion("unknown") + let image = self.image(forVersion: "unknown") expect(image.thumbnailURL()).to(beNil()) } it("handles incorrect image_versions JSON") { - let data:[String: AnyObject] = [ "id": id, "image_url": url, "image_versions": "something invalid"] + let data:[String: Any] = [ "id": id, "image_url": url, "image_versions": "something invalid"] expect(Image.fromJSON(data)).toNot( throwError() ) } diff --git a/KioskTests/Models/SaleTests.swift b/KioskTests/Models/SaleTests.swift index 44402356..1166e1db 100644 --- a/KioskTests/Models/SaleTests.swift +++ b/KioskTests/Models/SaleTests.swift @@ -2,7 +2,6 @@ import Quick import Nimble @testable import Kiosk -import ISO8601DateFormatter class SaleTests: QuickSpec { func stringFromDate(_ date: Date) -> String { @@ -17,7 +16,7 @@ class SaleTests: QuickSpec { let startDate = "2014-09-21T19:22:24Z" let endDate = "2015-09-24T19:22:24Z" let name = "name" - let data:[String: AnyObject] = ["id":id , "is_auction" : isAuction, "name": name, "start_at":startDate, "end_at":endDate] + let data:[String: Any] = ["id":id , "is_auction" : isAuction, "name": name, "start_at":startDate, "end_at":endDate] let sale = Sale.fromJSON(data) @@ -33,10 +32,10 @@ class SaleTests: QuickSpec { let artsyTime = SystemTime() artsyTime.systemTimeInterval = 0 - let date = NSDate.distantPast() + let date = NSDate.distantPast let dateString = self.stringFromDate(date) - let data:[String: AnyObject] = ["start_at": dateString, "end_at" : dateString] + let data:[String: AnyObject] = ["start_at": dateString as AnyObject, "end_at" : dateString as AnyObject] let sale = Sale.fromJSON(data) expect(sale.isActive(artsyTime)) == false @@ -46,13 +45,13 @@ class SaleTests: QuickSpec { let artsyTime = SystemTime() artsyTime.systemTimeInterval = 0 - let pastDate = NSDate.distantPast() + let pastDate = NSDate.distantPast let pastString = self.stringFromDate(pastDate) - let futureDate = NSDate.distantFuture() + let futureDate = NSDate.distantFuture let futureString = self.stringFromDate(futureDate) - let data:[String: AnyObject] = ["start_at": pastString, "end_at" : futureString] + let data:[String: AnyObject] = ["start_at": pastString as AnyObject, "end_at" : futureString as AnyObject] let sale = Sale.fromJSON(data) @@ -63,10 +62,10 @@ class SaleTests: QuickSpec { let artsyTime = SystemTime() artsyTime.systemTimeInterval = 0 - let date = NSDate.distantFuture() + let date = NSDate.distantFuture let dateString = self.stringFromDate(date) - let data:[String: AnyObject] = ["start_at": dateString, "end_at" : dateString] + let data:[String: AnyObject] = ["start_at": dateString as AnyObject, "end_at" : dateString as AnyObject] let sale = Sale.fromJSON(data) expect(sale.isActive(artsyTime)) == false diff --git a/KioskTests/Models/SystemTimeTests.swift b/KioskTests/Models/SystemTimeTests.swift index c0aa7333..c74fd964 100644 --- a/KioskTests/Models/SystemTimeTests.swift +++ b/KioskTests/Models/SystemTimeTests.swift @@ -36,7 +36,7 @@ class SystemTimeTests: QuickSpec { time .sync(networking) .subscribeNext { (_) in - let currentYear = yearFromDate(NSDate()) + let currentYear = yearFromDate(Date()) let timeYear = yearFromDate(time.date()) expect(timeYear) > currentYear @@ -55,7 +55,7 @@ class SystemTimeTests: QuickSpec { it("returns current time") { let time = SystemTime() - let currentYear = yearFromDate(NSDate()) + let currentYear = yearFromDate(Date()) let timeYear = yearFromDate(time.date()) expect(timeYear) == currentYear diff --git a/KioskTests/RegisterFlowViewTests.swift b/KioskTests/RegisterFlowViewTests.swift index c0f48784..1988e946 100644 --- a/KioskTests/RegisterFlowViewTests.swift +++ b/KioskTests/RegisterFlowViewTests.swift @@ -8,7 +8,7 @@ private let frame = CGRect(x: 0, y: 0, width: 180, height: 320) class RegisterFlowViewConfiguration: QuickConfiguration { override class func configure(_ configuration: Configuration) { - sharedExamples("a register flow view") { (sharedExampleContext: SharedExampleContext) in + sharedExamples("a register flow view") { (sharedExampleContext: @escaping SharedExampleContext) in var subject: RegisterFlowView! beforeEach { @@ -21,7 +21,7 @@ class RegisterFlowViewConfiguration: QuickConfiguration { subject.details = bidDetails - subject.snapshotViewAfterScreenUpdates(true) + subject.snapshotView(afterScreenUpdates: true) expect(subject).to( haveValidSnapshot() ) } @@ -34,7 +34,7 @@ class RegisterFlowViewConfiguration: QuickConfiguration { subject.details = bidDetails - subject.snapshotViewAfterScreenUpdates(true) + subject.snapshotView(afterScreenUpdates: true) expect(subject).to( haveValidSnapshot() ) } @@ -48,7 +48,7 @@ class RegisterFlowViewConfiguration: QuickConfiguration { subject.highlightedIndex.value = 2 subject.details = bidDetails - subject.snapshotViewAfterScreenUpdates(true) + subject.snapshotView(afterScreenUpdates: true) expect(subject).to( haveValidSnapshot() ) } @@ -63,7 +63,7 @@ class RegisterFlowViewConfiguration: QuickConfiguration { bidDetails.newUser.zipCode.value = "90210" subject.details = bidDetails - subject.snapshotViewAfterScreenUpdates(true) + subject.snapshotView(afterScreenUpdates: true) expect(subject).to( haveValidSnapshot() ) } } @@ -81,7 +81,7 @@ class RegisterFlowViewTests: QuickSpec { subject.constrainWidth("180") subject.constrainHeight("320") subject.appSetup = appSetup - subject.backgroundColor = .whiteColor() + subject.backgroundColor = .white } describe("requiring zip code") { diff --git a/KioskTests/SaleArtworkDetailsViewControllerTests.swift b/KioskTests/SaleArtworkDetailsViewControllerTests.swift index 1d8806fe..c18b3723 100644 --- a/KioskTests/SaleArtworkDetailsViewControllerTests.swift +++ b/KioskTests/SaleArtworkDetailsViewControllerTests.swift @@ -7,7 +7,7 @@ import SDWebImage class SaleArtworkDetailsViewControllerConfiguration: QuickConfiguration { override class func configure(_ configuration: Configuration) { - sharedExamples("a sale artwork details view controller") { (sharedExampleContext: SharedExampleContext) in + sharedExamples("a sale artwork details view controller") { (sharedExampleContext: @escaping SharedExampleContext) in var subject: SaleArtworkDetailsViewController! beforeEach{ @@ -31,7 +31,7 @@ class SaleArtworkDetailsViewControllerTests: QuickSpec { subject.allowAnimations = false let image = UIImage.testImage(named: "artwork", ofType: "jpg") - self.imageCache.storeImage(image, forKey: "http://example.com/large.jpg") + self.imageCache?.store(image, forKey: "http://example.com/large.jpg") } describe("without lot numbers") { diff --git a/KioskTests/SwitchViewSpec.swift b/KioskTests/SwitchViewSpec.swift index d7ccc216..346045e0 100644 --- a/KioskTests/SwitchViewSpec.swift +++ b/KioskTests/SwitchViewSpec.swift @@ -9,7 +9,7 @@ class SwitchViewSpec: QuickSpec { it("looks correct configured with two buttons") { let titles = ["First title", "Second Title"] let switchView = SwitchView(buttonTitles: titles) - switchView.frame = CGRect(origin: CGPointZero, size: CGSize(width: 400, height: switchView.intrinsicContentSize().height)) + switchView.frame = CGRect(origin: CGPoint.zero, size: CGSize(width: 400, height: switchView.intrinsicContentSize.height)) expect(switchView).to(haveValidSnapshot(named:"default")) } diff --git a/KioskTests/TestHelpers.swift b/KioskTests/TestHelpers.swift index 05014ff3..190cfa03 100644 --- a/KioskTests/TestHelpers.swift +++ b/KioskTests/TestHelpers.swift @@ -41,7 +41,7 @@ func yearFromDate(_ date: Date) -> Int { extension UIImage { class func testImage(named name: String, ofType type: String) -> UIImage! { let bundle = Bundle(for: type(of: TestClass())) - let path = bundle.pathForResource(name, ofType: type) + let path = bundle.path(forResource: name, ofType: type) return UIImage(contentsOfFile: path!) } } @@ -87,14 +87,6 @@ class StubFulfillmentController: FulfillmentController { var xAccessToken: String? } -/// Nimble is currently having issues with nondeterministic async expectations. -/// This will have to do for now 😢 -/// See: https://github.com/Quick/Nimble/issues/177 -func kioskWaitUntil(_ action: (() -> Void) -> Void) { - waitUntil(timeout: 10, action: action) -} - - // TODO: Move these into a separate pod? // This is handy so we can write expect(o) == 1 instead of expect(o.value) == 1 or whatever. public func equalFirst(_ expectedValue: T?) -> MatcherFunc> { @@ -126,9 +118,9 @@ public func equalFirst(_ expectedValue: T?) -> MatcherFunc(_ expectedValue: T?) -> MatcherFunc CocoaAction { return CocoaAction { _ in Observable.empty() } @@ -182,7 +174,7 @@ func neverAction() -> CocoaAction { return CocoaAction { _ in Observable.never() } } -func errorAction(_ error: ErrorType = TestError.Default) -> CocoaAction { +func errorAction(_ error: Swift.Error = TestError.Default) -> CocoaAction { return CocoaAction { _ in Observable.error(error) } } diff --git a/KioskTests/TextFieldTests.swift b/KioskTests/TextFieldTests.swift index 93753174..58d8da3b 100644 --- a/KioskTests/TextFieldTests.swift +++ b/KioskTests/TextFieldTests.swift @@ -10,9 +10,9 @@ class TextFieldTests: QuickSpec { pending("TextField") { var textField: TextField? beforeEach { - let window = UIWindow(frame:UIScreen.mainScreen().bounds) + let window = UIWindow(frame:UIScreen.main.bounds) let vc = UIViewController() - textField = TextField(frame: CGRectMake(0, 0, 255, 44)) + textField = TextField(frame: CGRect(x: 0, y: 0, width: 255, height: 44)) textField!.shouldAnimateStateChange = false vc.view.addSubview(textField!) window.rootViewController = vc @@ -34,12 +34,12 @@ class TextFieldTests: QuickSpec { pending("SecureTextField") { var textField: SecureTextField? beforeEach { - let window = UIWindow(frame:UIScreen.mainScreen().bounds) + let window = UIWindow(frame:UIScreen.main.bounds) let vc = UIViewController() - textField = SecureTextField(frame: CGRectMake(0, 0, 255, 44)) + textField = SecureTextField(frame: CGRect(x: 0, y: 0, width: 255, height: 44)) textField!.shouldAnimateStateChange = false textField!.text = "" - textField!.font = UIFont.serifFontWithSize(textField!.font!.pointSize) + textField!.font = UIFont.serifFont(withSize: textField!.font!.pointSize) vc.view.addSubview(textField!) window.rootViewController = vc window.makeKeyAndVisible() diff --git a/KioskTests/UILabelExtensionsTests.swift b/KioskTests/UILabelExtensionsTests.swift index fbd95f61..b3203e45 100644 --- a/KioskTests/UILabelExtensionsTests.swift +++ b/KioskTests/UILabelExtensionsTests.swift @@ -7,18 +7,18 @@ class UILabelExtensionsTests: QuickSpec { override func spec() { it("makes labels non-opaque") { let subject = UILabel() - subject.opaque = true + subject.isOpaque = true subject.makeTransparent() - expect(subject.opaque) == false + expect(subject.isOpaque) == false } it("makes labels with clear backgrounds") { let subject = UILabel() - subject.backgroundColor = .redColor() + subject.backgroundColor = .red subject.makeTransparent() - expect(subject.backgroundColor) == .clearColor() + expect(subject.backgroundColor) == .clear } } } diff --git a/KioskTests/XAppTokenSpec.swift b/KioskTests/XAppTokenSpec.swift index 90f0a75a..07dc880c 100644 --- a/KioskTests/XAppTokenSpec.swift +++ b/KioskTests/XAppTokenSpec.swift @@ -5,17 +5,17 @@ import Kiosk class XAppTokenSpec: QuickSpec { override func spec() { - var defaults: NSUserDefaults! + var defaults: UserDefaults! var token: XAppToken! beforeEach { - defaults = NSUserDefaults() + defaults = UserDefaults() token = XAppToken(defaults: defaults) } it("returns correct data") { let key = "some key" - let expiry = NSDate(timeIntervalSinceNow: 1000) + let expiry = Date(timeIntervalSinceNow: 1000) setDefaultsKeys(defaults, key: key, expiry: expiry) expect(token.token).to( equal(key) ) @@ -24,7 +24,7 @@ class XAppTokenSpec: QuickSpec { it("correctly calculates validity for expired tokens") { let key = "some key" - let past = NSDate(timeIntervalSinceNow: -1000) + let past = Date(timeIntervalSinceNow: -1000) setDefaultsKeys(defaults, key: key, expiry: past) expect(token.isValid).to( beFalsy() ) @@ -32,7 +32,7 @@ class XAppTokenSpec: QuickSpec { it("correctly calculates validity for non-expired tokens") { let key = "some key" - let future = NSDate(timeIntervalSinceNow: 1000) + let future = Date(timeIntervalSinceNow: 1000) setDefaultsKeys(defaults, key: key, expiry: future) expect(token.isValid).to( beTruthy() ) @@ -40,7 +40,7 @@ class XAppTokenSpec: QuickSpec { it("correctly calculates validity for empty keys") { let key = "" - let future = NSDate(timeIntervalSinceNow: 1000) + let future = Date(timeIntervalSinceNow: 1000) setDefaultsKeys(defaults, key: key, expiry: future) expect(token.isValid).to( beFalsy() ) diff --git a/Podfile b/Podfile index 4d08dc2e..3cd64dd2 100644 --- a/Podfile +++ b/Podfile @@ -69,8 +69,7 @@ target 'Kiosk' do target 'KioskTests' do inherit! :search_paths - # To get around this issue: https://github.com/facebook/ios-snapshot-test-case/issues/167 - pod 'FBSnapshotTestCase', :git => 'https://github.com/facebook/ios-snapshot-test-case', :commit => '1639f694a2100cdeadf2f8fa14225cdd3759e75b' + pod 'FBSnapshotTestCase' pod 'Nimble-Snapshots' pod 'Quick' pod 'Nimble' diff --git a/Podfile.lock b/Podfile.lock index fda1d0fc..2efda5db 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -28,10 +28,10 @@ PODS: - DZNWebViewController (2.0): - NJKWebViewProgress (~> 0.2) - ECPhoneNumberFormatter (0.1.1) - - FBSnapshotTestCase (2.1.0): - - FBSnapshotTestCase/SwiftSupport (= 2.1.0) - - FBSnapshotTestCase/Core (2.1.0) - - FBSnapshotTestCase/SwiftSupport (2.1.0): + - FBSnapshotTestCase (2.1.4): + - FBSnapshotTestCase/SwiftSupport (= 2.1.4) + - FBSnapshotTestCase/Core (2.1.4) + - FBSnapshotTestCase/SwiftSupport (2.1.4): - FBSnapshotTestCase/Core - FLKAutoLayout (0.1.1) - fmemopen (0.0.1) @@ -50,7 +50,7 @@ PODS: - Moya/Core - RxSwift - Nimble (5.0.0) - - Nimble-Snapshots (4.1.0): + - Nimble-Snapshots (4.2.0): - FBSnapshotTestCase (~> 2.0) - Nimble - Quick @@ -97,7 +97,7 @@ DEPENDENCIES: - CardFlight - DZNWebViewController (from `https://github.com/orta/DZNWebViewController.git`) - ECPhoneNumberFormatter - - FBSnapshotTestCase (from `https://github.com/facebook/ios-snapshot-test-case`, commit `1639f694a2100cdeadf2f8fa14225cdd3759e75b`) + - FBSnapshotTestCase - FLKAutoLayout (= 0.1.1) - Forgeries - Keys (from `Pods/CocoaPodsKeys`) @@ -122,9 +122,6 @@ DEPENDENCIES: EXTERNAL SOURCES: DZNWebViewController: :git: https://github.com/orta/DZNWebViewController.git - FBSnapshotTestCase: - :commit: 1639f694a2100cdeadf2f8fa14225cdd3759e75b - :git: https://github.com/facebook/ios-snapshot-test-case Keys: :path: Pods/CocoaPodsKeys Reachability: @@ -137,9 +134,6 @@ CHECKOUT OPTIONS: DZNWebViewController: :commit: 9121386901af95072fb19eaa3df7060ca46fc337 :git: https://github.com/orta/DZNWebViewController.git - FBSnapshotTestCase: - :commit: 1639f694a2100cdeadf2f8fa14225cdd3759e75b - :git: https://github.com/facebook/ios-snapshot-test-case Reachability: :commit: 6db8c29565c319f59174dd63e4b1ac5b471d133a :git: https://github.com/ashfurrow/Reachability.git @@ -160,7 +154,7 @@ SPEC CHECKSUMS: CardFlight: c27f6b57053ff7ee1918fd8130af289eef97195e DZNWebViewController: 54e8ee1c5bcc43e341ad77737631098d0d76db69 ECPhoneNumberFormatter: 061041e7715f8d3f2e8e2a069ddf28c52f7bd314 - FBSnapshotTestCase: 366ecd378511d7716c79991cd8067d1eed23578d + FBSnapshotTestCase: 094f9f314decbabe373b87cc339bea235a63e07a FLKAutoLayout: 95b12c5cd9652100235140b68652f063f7437851 fmemopen: dc31f7d3004644b33deee95e047305d23f8cfdd4 Forgeries: 64ced144ea8341d89a7eec9d1d7986f0f1366250 @@ -169,7 +163,7 @@ SPEC CHECKSUMS: Mixpanel: 4a2330c26556109d2f42b01e36a05eb9e2caf25e Moya: 920d40f738e6f4ece84010cec701bb9693be8251 Nimble: 56fc9f5020effa2206de22c3dd910f4fb011b92f - Nimble-Snapshots: 2da4d2ec829b458d6886c06812c52493fce284c9 + Nimble-Snapshots: 4826e3ee211d2c7af27645e35d68b9e9917882e7 NJKWebViewProgress: f481fd424cb5ecc27eacae11b5397db92fba9a4d NSObject+Rx: 2a9cd801d9c847e6d2486cbad8d7701b67834e70 ORStackView: 5df6b1b990b0648d8ef6f69f89361ea8648e8f64 @@ -187,6 +181,6 @@ SPEC CHECKSUMS: UIView+BooleanAnimations: a760be9a066036e55f298b7b7350a6cb14cfcd97 XNGMarkdownParser: 562999edfa0e0913dc345f0931bdd78be59f826e -PODFILE CHECKSUM: baab7f8e9be67d7b89c9de732d1b94fd83a28c1b +PODFILE CHECKSUM: ed9e7da6227ef668413ca6fe137faa2a4f1a1173 COCOAPODS: 1.1.0.rc.2 From 71bbb27f6418f651ab41721425a25562e30ef12b Mon Sep 17 00:00:00 2001 From: Ash Furrow Date: Fri, 7 Oct 2016 15:13:06 -0400 Subject: [PATCH 09/46] [Swift 3] Fixes some initial runtime errors. --- .../xcshareddata/xcschemes/Kiosk.xcscheme | 7 +++++++ Kiosk/App/AppDelegate.swift | 2 +- Kiosk/App/Models/Artwork.swift | 19 ++++++++++--------- Kiosk/App/Models/SaleArtworkViewModel.swift | 2 +- Kiosk/ListingsCollectionViewCell.swift | 6 ++++-- Kiosk/Observable+Operators.swift | 2 +- Podfile.lock | 12 ++++++------ 7 files changed, 30 insertions(+), 20 deletions(-) diff --git a/Kiosk.xcodeproj/xcshareddata/xcschemes/Kiosk.xcscheme b/Kiosk.xcodeproj/xcshareddata/xcschemes/Kiosk.xcscheme index 3de74d31..6ad87980 100644 --- a/Kiosk.xcodeproj/xcshareddata/xcschemes/Kiosk.xcscheme +++ b/Kiosk.xcodeproj/xcshareddata/xcschemes/Kiosk.xcscheme @@ -85,6 +85,13 @@ ReferencedContainer = "container:Kiosk.xcodeproj"> + + + + diff --git a/Kiosk/App/AppDelegate.swift b/Kiosk/App/AppDelegate.swift index 0ee559d8..e8b8432d 100644 --- a/Kiosk/App/AppDelegate.swift +++ b/Kiosk/App/AppDelegate.swift @@ -58,7 +58,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { ARAnalytics.setup(withAnalytics: [ ARHockeyAppBetaID: keys.hockeyBetaSecret(), ARHockeyAppLiveID: keys.hockeyProductionSecret(), - ARMixpanelToken: mixpanelToken +// ARMixpanelToken: mixpanelToken // TODO: Restore mixpanel ]) setupHelpButton() diff --git a/Kiosk/App/Models/Artwork.swift b/Kiosk/App/Models/Artwork.swift index 9ef05c68..3ff648c7 100644 --- a/Kiosk/App/Models/Artwork.swift +++ b/Kiosk/App/Models/Artwork.swift @@ -21,7 +21,9 @@ final class Artwork: NSObject, JSONAbleType { let dateString: String dynamic let title: String - dynamic let titleAndDate: NSAttributedString + var titleAndDate: NSAttributedString { + return titleAndDateAttributedString(self.title, dateString: self.date) + } dynamic let price: String dynamic let date: String @@ -44,11 +46,10 @@ final class Artwork: NSObject, JSONAbleType { return defaultImages?.first ?? self.images?.first }() - init(id: String, dateString: String, title: String, titleAndDate: NSAttributedString, price: String, date: String, sold: String) { + init(id: String, dateString: String, title: String, price: String, date: String, sold: String) { self.id = id self.dateString = dateString self.title = title - self.titleAndDate = titleAndDate self.price = price self.date = date self.soldStatus = sold @@ -63,9 +64,8 @@ final class Artwork: NSObject, JSONAbleType { let price = json["price"].stringValue let date = json["date"].stringValue let sold = json["sold"].stringValue - let titleAndDate = titleAndDateAttributedString(title, dateString: dateString) - let artwork = Artwork(id: id, dateString: dateString, title: title, titleAndDate:titleAndDate, price: price, date: date, sold: sold) + let artwork = Artwork(id: id, dateString: dateString, title: title, price: price, date: date, sold: sold) artwork.additionalInfo = json["additional_information"].string artwork.medium = json["medium"].string @@ -108,12 +108,13 @@ final class Artwork: NSObject, JSONAbleType { private func titleAndDateAttributedString(_ title: String, dateString: String) -> NSAttributedString { let workTitle = title.isEmpty ? "Untitled" : title - let workFont = UIFont.serifItalicFont(withSize: 16) - let attributedString = NSMutableAttributedString(string: workTitle, attributes: [NSFontAttributeName : workFont ]) + + let workFont = UIFont.serifItalicFont(withSize: 16)! + let attributedString = NSMutableAttributedString(string: workTitle, attributes: [NSFontAttributeName : workFont]) if dateString.isNotEmpty { - let dateFont = UIFont.serifFont(withSize: 16) - let dateString = NSMutableAttributedString(string: ", " + dateString, attributes: [ NSFontAttributeName : dateFont ]) + let dateFont = UIFont.serifFont(withSize: 16)! + let dateString = NSAttributedString(string: ", " + dateString, attributes: [NSFontAttributeName : dateFont]) attributedString.append(dateString) } diff --git a/Kiosk/App/Models/SaleArtworkViewModel.swift b/Kiosk/App/Models/SaleArtworkViewModel.swift index 8117779f..c874ebf8 100644 --- a/Kiosk/App/Models/SaleArtworkViewModel.swift +++ b/Kiosk/App/Models/SaleArtworkViewModel.swift @@ -47,7 +47,7 @@ extension SaleArtworkViewModel { return saleArtwork.artwork.artists?.first?.name } - var titleAndDateAttributedString: NSAttributedString? { + var titleAndDateAttributedString: NSAttributedString { return saleArtwork.artwork.titleAndDate } diff --git a/Kiosk/ListingsCollectionViewCell.swift b/Kiosk/ListingsCollectionViewCell.swift index 82450477..30e62a7b 100644 --- a/Kiosk/ListingsCollectionViewCell.swift +++ b/Kiosk/ListingsCollectionViewCell.swift @@ -105,8 +105,10 @@ class ListingsCollectionViewCell: UICollectionViewCell { .bindTo(artistNameLabel.rx.text) .addDisposableTo(reuseBag) - viewModel.map { $0.titleAndDateAttributedString ?? NSAttributedString() } - .bindTo(artworkTitleLabel.rx.attributedText) + viewModel.map { $0.titleAndDateAttributedString } + .subscribe(onNext: { [weak self] (attributedString) in + self?.artworkTitleLabel.attributedText = attributedString + }) .addDisposableTo(reuseBag) viewModel.map { $0.estimateString } diff --git a/Kiosk/Observable+Operators.swift b/Kiosk/Observable+Operators.swift index dd2383ba..da0fe080 100644 --- a/Kiosk/Observable+Operators.swift +++ b/Kiosk/Observable+Operators.swift @@ -52,7 +52,7 @@ extension Observable where Element: OptionalType { func filterNilKeepOptional() -> Observable { return self.filter { (element) -> Bool in - return element != nil + return element.value != nil } } diff --git a/Podfile.lock b/Podfile.lock index 2efda5db..85662e49 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -3,11 +3,11 @@ PODS: - RxCocoa - RxSwift - Alamofire (4.0.1) - - ARAnalytics/CoreIOS (4.0.0) - - ARAnalytics/HockeyApp (4.0.0): + - ARAnalytics/CoreIOS (4.0.1) + - ARAnalytics/HockeyApp (4.0.1): - ARAnalytics/CoreIOS - HockeySDK-Source - - ARAnalytics/Mixpanel (4.0.0): + - ARAnalytics/Mixpanel (4.0.1): - ARAnalytics/CoreIOS - Mixpanel - ARCollectionViewMasonryLayout (2.0.0) @@ -38,7 +38,7 @@ PODS: - Forgeries (1.0.0): - Forgeries/Core (= 1.0.0) - Forgeries/Core (1.0.0) - - HockeySDK-Source (4.1.1) + - HockeySDK-Source (4.1.2) - Keys (1.0.0) - Mixpanel (3.0.4): - Mixpanel/Mixpanel (= 3.0.4) @@ -144,7 +144,7 @@ CHECKOUT OPTIONS: SPEC CHECKSUMS: Action: 13a8e52481834751caf428bcf6c527e3db2aefc7 Alamofire: 7682d43245de14874acd142ec137b144aa1dd335 - ARAnalytics: d8a0eee8da481d146ca8af5a9ac632b5c4d17a81 + ARAnalytics: 85e7bb999e2a40cea7d2394e71e8ecbafa50f2d0 ARCollectionViewMasonryLayout: 164b82010cf8ec99bc7a38cffe59a179d7e5a116 ARTiledImageView: 8c6fd11d9e8459a853d94394adea3a5e8c9329ba Artsy+UIColors: 10a2d71e9ffe1015be43d4e084d13ff4337aab97 @@ -158,7 +158,7 @@ SPEC CHECKSUMS: FLKAutoLayout: 95b12c5cd9652100235140b68652f063f7437851 fmemopen: dc31f7d3004644b33deee95e047305d23f8cfdd4 Forgeries: 64ced144ea8341d89a7eec9d1d7986f0f1366250 - HockeySDK-Source: a86f75afee838fc50826eb7a17875dea66844a37 + HockeySDK-Source: 58d09800c495347a144e0bd7e7c7087c7831f617 Keys: 9c35bf00f612ee1d48556f4a4b9b4551e224e90f Mixpanel: 4a2330c26556109d2f42b01e36a05eb9e2caf25e Moya: 920d40f738e6f4ece84010cec701bb9693be8251 From 8538c749f9d5070fc45299aaef62589ca9e1bcc5 Mon Sep 17 00:00:00 2001 From: Ash Furrow Date: Fri, 7 Oct 2016 15:55:36 -0400 Subject: [PATCH 10/46] [Swift 3] Replaces ISO8601DateFormatter. --- Kiosk.xcodeproj/project.pbxproj | 6 ++++++ Kiosk/App/KioskDateFormatter.h | 7 +++++++ Kiosk/App/KioskDateFormatter.m | 11 +++++++++++ Kiosk/App/Models/BidderPosition.swift | 3 +-- Kiosk/App/Models/Sale.swift | 5 ++--- Kiosk/App/Models/SystemTime.swift | 5 ++--- Kiosk/App/Networking/Networking.swift | 3 +-- Kiosk/ListingsCollectionViewCell.swift | 5 ++--- Kiosk/Supporting Files/BridgingHeader.h | 1 + KioskTests/ArtsyAPISpec.swift | 11 +++++++---- Podfile | 1 + Podfile.lock | 5 ++++- 12 files changed, 45 insertions(+), 18 deletions(-) create mode 100644 Kiosk/App/KioskDateFormatter.h create mode 100644 Kiosk/App/KioskDateFormatter.m diff --git a/Kiosk.xcodeproj/project.pbxproj b/Kiosk.xcodeproj/project.pbxproj index b7990353..4c1b7e99 100644 --- a/Kiosk.xcodeproj/project.pbxproj +++ b/Kiosk.xcodeproj/project.pbxproj @@ -188,6 +188,7 @@ 5E8611491A5C442C00456E41 /* XApp.json in Resources */ = {isa = PBXBuildFile; fileRef = 5E8610451A5C429C00456E41 /* XApp.json */; }; 5E86114A1A5C442C00456E41 /* XAuth.json in Resources */ = {isa = PBXBuildFile; fileRef = 5E8610461A5C429C00456E41 /* XAuth.json */; }; 5E87BBA81C03C39F00D46BB5 /* Observable+JSONAble.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E87BBA71C03C39F00D46BB5 /* Observable+JSONAble.swift */; }; + 5E88FB001DA833A7001E12E0 /* KioskDateFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E88FAFF1DA833A7001E12E0 /* KioskDateFormatter.m */; }; 5E8FABE81A5C49D800F6B913 /* StubResponses.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E860FED1A5C429C00456E41 /* StubResponses.m */; }; 5E9F8AD41A8951C100CBEACE /* SaleArtworkDetailsViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E9F8AD31A8951C100CBEACE /* SaleArtworkDetailsViewControllerTests.swift */; }; 5EA1F7291BFBC84E00F90655 /* HelperFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EA1F7281BFBC84E00F90655 /* HelperFunctions.swift */; }; @@ -401,6 +402,8 @@ 5E8611001A5C43EF00456E41 /* TextFieldTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldTests.swift; sourceTree = ""; }; 5E8611011A5C43EF00456E41 /* XAppTokenSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XAppTokenSpec.swift; sourceTree = ""; }; 5E87BBA71C03C39F00D46BB5 /* Observable+JSONAble.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Observable+JSONAble.swift"; sourceTree = ""; }; + 5E88FAFE1DA833A7001E12E0 /* KioskDateFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KioskDateFormatter.h; sourceTree = ""; }; + 5E88FAFF1DA833A7001E12E0 /* KioskDateFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KioskDateFormatter.m; sourceTree = ""; }; 5E8B22F41DA445CE008D1EB4 /* Gemfile */ = {isa = PBXFileReference; lastKnownFileType = text; path = Gemfile; sourceTree = ""; }; 5E9F8AD31A8951C100CBEACE /* SaleArtworkDetailsViewControllerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SaleArtworkDetailsViewControllerTests.swift; sourceTree = ""; }; 5EA1F7281BFBC84E00F90655 /* HelperFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HelperFunctions.swift; sourceTree = ""; }; @@ -508,6 +511,8 @@ 5E860FF11A5C429C00456E41 /* Views */, 5EF71E791AE98F080069A266 /* STPCard+Validation.h */, 5EF71E7A1AE98F080069A266 /* STPCard+Validation.m */, + 5E88FAFE1DA833A7001E12E0 /* KioskDateFormatter.h */, + 5E88FAFF1DA833A7001E12E0 /* KioskDateFormatter.m */, ); path = App; sourceTree = ""; @@ -1167,6 +1172,7 @@ 5EBA94591BF2B11400DE3045 /* Observable+Operators.swift in Sources */, 5E8610901A5C429C00456E41 /* LoadingViewController.swift in Sources */, 5E8610841A5C429C00456E41 /* BidDetailsPreviewView.swift in Sources */, + 5E88FB001DA833A7001E12E0 /* KioskDateFormatter.m in Sources */, 5E8610791A5C429C00456E41 /* SwitchView.swift in Sources */, 5E8610611A5C429C00456E41 /* Image.swift in Sources */, 5E8610661A5C429C00456E41 /* SystemTime.swift in Sources */, diff --git a/Kiosk/App/KioskDateFormatter.h b/Kiosk/App/KioskDateFormatter.h new file mode 100644 index 00000000..272cd217 --- /dev/null +++ b/Kiosk/App/KioskDateFormatter.h @@ -0,0 +1,7 @@ +#import + +@interface KioskDateFormatter :NSObject + ++ (NSDate * __nullable)fromString:(NSString * _Nonnull)string; + +@end diff --git a/Kiosk/App/KioskDateFormatter.m b/Kiosk/App/KioskDateFormatter.m new file mode 100644 index 00000000..a482b1e0 --- /dev/null +++ b/Kiosk/App/KioskDateFormatter.m @@ -0,0 +1,11 @@ +#import "KioskDateFormatter.h" +@import ISO8601DateFormatter; + +@implementation KioskDateFormatter + ++ (NSDate *)fromString:(NSString *)string { + ISO8601DateFormatter *formatter = [[ISO8601DateFormatter alloc] init]; + return [formatter dateFromString:string]; +} + +@end diff --git a/Kiosk/App/Models/BidderPosition.swift b/Kiosk/App/Models/BidderPosition.swift index 7697a74f..90bda28c 100644 --- a/Kiosk/App/Models/BidderPosition.swift +++ b/Kiosk/App/Models/BidderPosition.swift @@ -16,11 +16,10 @@ final class BidderPosition: NSObject, JSONAbleType { static func fromJSON(_ source:[String: Any]) -> BidderPosition { let json = JSON(source) - let formatter = ISO8601DateFormatter() let id = json["id"].stringValue let maxBidAmount = json["max_bid_amount_cents"].intValue - let processedAt = formatter.date(from: json["processed_at"].stringValue) + let processedAt = KioskDateFormatter.fromString(json["processed_at"].stringValue) var bid: Bid? if let bidDictionary = json["highest_bid"].object as? [String: AnyObject] { diff --git a/Kiosk/App/Models/Sale.swift b/Kiosk/App/Models/Sale.swift index a05156d6..930015f3 100644 --- a/Kiosk/App/Models/Sale.swift +++ b/Kiosk/App/Models/Sale.swift @@ -24,12 +24,11 @@ final class Sale: NSObject, JSONAbleType { static func fromJSON(_ json:[String: Any]) -> Sale { let json = JSON(json) - let formatter = ISO8601DateFormatter() let id = json["id"].stringValue let isAuction = json["is_auction"].boolValue - let startDate = formatter.date(from: json["start_at"].stringValue)! - let endDate = formatter.date(from: json["end_at"].stringValue)! + let startDate = KioskDateFormatter.fromString(json["start_at"].stringValue)! + let endDate = KioskDateFormatter.fromString(json["end_at"].stringValue)! let name = json["name"].stringValue let artworkCount = json["eligible_sale_artworks_count"].intValue let state = json["auction_state"].stringValue diff --git a/Kiosk/App/Models/SystemTime.swift b/Kiosk/App/Models/SystemTime.swift index b5f86c80..395e2680 100644 --- a/Kiosk/App/Models/SystemTime.swift +++ b/Kiosk/App/Models/SystemTime.swift @@ -14,11 +14,10 @@ class SystemTime { .mapJSON() .doOnNext { [weak self] response in guard let dictionary = response as? NSDictionary else { return } - let formatter = ISO8601DateFormatter() let timestamp: String = (dictionary["iso8601"] as? String) ?? "" - if let artsyDate = formatter.date(from: timestamp) { - self?.systemTimeInterval = NSDate().timeIntervalSince(artsyDate) + if let artsyDate = KioskDateFormatter.fromString(timestamp) { + self?.systemTimeInterval = Date().timeIntervalSince(artsyDate) } }.logError().map(void) diff --git a/Kiosk/App/Networking/Networking.swift b/Kiosk/App/Networking/Networking.swift index 6c84c0d8..97b93dcf 100644 --- a/Kiosk/App/Networking/Networking.swift +++ b/Kiosk/App/Networking/Networking.swift @@ -69,10 +69,9 @@ private extension Networking { .doOn { event in guard case RxSwift.Event.next(let element) = event else { return } - let formatter = ISO8601DateFormatter() // These two lines set the defaults values injected into appToken appToken.token = element.0 - appToken.expiry = formatter.date(from: element.1 ?? "") + appToken.expiry = KioskDateFormatter.fromString(element.1 ?? "") } .map { (token, expiry) -> String? in return token diff --git a/Kiosk/ListingsCollectionViewCell.swift b/Kiosk/ListingsCollectionViewCell.swift index 30e62a7b..b81ed002 100644 --- a/Kiosk/ListingsCollectionViewCell.swift +++ b/Kiosk/ListingsCollectionViewCell.swift @@ -106,9 +106,8 @@ class ListingsCollectionViewCell: UICollectionViewCell { .addDisposableTo(reuseBag) viewModel.map { $0.titleAndDateAttributedString } - .subscribe(onNext: { [weak self] (attributedString) in - self?.artworkTitleLabel.attributedText = attributedString - }) + .mapToOptional() + .bindTo(artworkTitleLabel.rx.attributedText) .addDisposableTo(reuseBag) viewModel.map { $0.estimateString } diff --git a/Kiosk/Supporting Files/BridgingHeader.h b/Kiosk/Supporting Files/BridgingHeader.h index 5e9be90e..1596cb97 100644 --- a/Kiosk/Supporting Files/BridgingHeader.h +++ b/Kiosk/Supporting Files/BridgingHeader.h @@ -5,3 +5,4 @@ #import "StubResponses.h" #import "STPCard+Validation.h" +#import "KioskDateFormatter.h" diff --git a/KioskTests/ArtsyAPISpec.swift b/KioskTests/ArtsyAPISpec.swift index 25924499..5f646277 100644 --- a/KioskTests/ArtsyAPISpec.swift +++ b/KioskTests/ArtsyAPISpec.swift @@ -72,10 +72,13 @@ class ArtsyAPISpec: QuickSpec { it("gets XApp token if it doesn't exist yet") { setDefaultsKeys(defaults, key: nil, expiry: nil) - - newXAppRequest().subscribeNext { (object) in - // nop - }.addDisposableTo(disposeBag) + + waitUntil { done in + newXAppRequest().subscribeNext { (object) in + // nop + done() + }.addDisposableTo(disposeBag) + } let past = Date(timeIntervalSinceNow: -1000) expect(getDefaultsKeys(defaults).key).to(equal("STUBBED TOKEN!")) diff --git a/Podfile b/Podfile index 3cd64dd2..2f5ded2f 100644 --- a/Podfile +++ b/Podfile @@ -57,6 +57,7 @@ target 'Kiosk' do pod 'UIView+BooleanAnimations' pod 'ARTiledImageView' pod 'XNGMarkdownParser' + pod 'ISO8601DateFormatter' # Swift pods pod 'SwiftyJSON' diff --git a/Podfile.lock b/Podfile.lock index 85662e49..d3e0ed67 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -39,6 +39,7 @@ PODS: - Forgeries/Core (= 1.0.0) - Forgeries/Core (1.0.0) - HockeySDK-Source (4.1.2) + - ISO8601DateFormatter (0.8) - Keys (1.0.0) - Mixpanel (3.0.4): - Mixpanel/Mixpanel (= 3.0.4) @@ -100,6 +101,7 @@ DEPENDENCIES: - FBSnapshotTestCase - FLKAutoLayout (= 0.1.1) - Forgeries + - ISO8601DateFormatter - Keys (from `Pods/CocoaPodsKeys`) - Moya/RxSwift (= 8.0.0-beta.2) - Nimble @@ -159,6 +161,7 @@ SPEC CHECKSUMS: fmemopen: dc31f7d3004644b33deee95e047305d23f8cfdd4 Forgeries: 64ced144ea8341d89a7eec9d1d7986f0f1366250 HockeySDK-Source: 58d09800c495347a144e0bd7e7c7087c7831f617 + ISO8601DateFormatter: 4551b6ce4f83185425f583b0b3feb3c7b59b942c Keys: 9c35bf00f612ee1d48556f4a4b9b4551e224e90f Mixpanel: 4a2330c26556109d2f42b01e36a05eb9e2caf25e Moya: 920d40f738e6f4ece84010cec701bb9693be8251 @@ -181,6 +184,6 @@ SPEC CHECKSUMS: UIView+BooleanAnimations: a760be9a066036e55f298b7b7350a6cb14cfcd97 XNGMarkdownParser: 562999edfa0e0913dc345f0931bdd78be59f826e -PODFILE CHECKSUM: ed9e7da6227ef668413ca6fe137faa2a4f1a1173 +PODFILE CHECKSUM: 8857e47286c312236f36121cf3291303c46c6dc7 COCOAPODS: 1.1.0.rc.2 From d26b7b2519996eded0e15a71dc7f3e99e620792e Mon Sep 17 00:00:00 2001 From: Ash Furrow Date: Fri, 7 Oct 2016 16:17:47 -0400 Subject: [PATCH 11/46] [Swift 3] Updates IUO handling. --- Kiosk/App/Models/SaleArtworkViewModel.swift | 12 +++++++----- .../BidDetailsPreviewView.swift | 2 +- .../ConfirmYourBidViewController.swift | 2 +- .../PlaceBidViewController.swift | 2 +- Kiosk/HelperFunctions.swift | 2 +- ...help_view_controller__looks_correct@2x.png | Bin 168973 -> 169076 bytes ...help_view_controller__looks_correct@2x.png | Bin 172447 -> 172895 bytes 7 files changed, 11 insertions(+), 9 deletions(-) diff --git a/Kiosk/App/Models/SaleArtworkViewModel.swift b/Kiosk/App/Models/SaleArtworkViewModel.swift index c874ebf8..514ee116 100644 --- a/Kiosk/App/Models/SaleArtworkViewModel.swift +++ b/Kiosk/App/Models/SaleArtworkViewModel.swift @@ -20,15 +20,15 @@ extension SaleArtworkViewModel { var estimateString: String { // Default to estimateCents if let estimateCents = saleArtwork.estimateCents { - let dollars = NumberFormatter.currencyString(forCents: estimateCents as NSNumber!) + let dollars = NumberFormatter.currencyString(forDollarCents: estimateCents as NSNumber!) ?? "" return "Estimate: \(dollars)" } // Try to extract non-nil low/high estimates. Return a default otherwise. switch (saleArtwork.lowEstimateCents, saleArtwork.highEstimateCents) { case let (.some(lowCents), .some(highCents)): - let lowDollars = NumberFormatter.currencyString(forCents: lowCents as NSNumber!) - let highDollars = NumberFormatter.currencyString(forCents: highCents as NSNumber!) + let lowDollars = NumberFormatter.currencyString(forDollarCents: lowCents as NSNumber!) ?? "" + let highDollars = NumberFormatter.currencyString(forDollarCents: highCents as NSNumber!) ?? "" return "Estimate: \(lowDollars)–\(highDollars)" default: return "No Estimate" @@ -123,9 +123,11 @@ extension SaleArtworkViewModel { func currentBid(prefix: String = "", missingPrefix: String = "") -> Observable { return saleArtwork.rx.observe(NSNumber.self, "highestBidCents").map { [weak self] highestBidCents in if let currentBidCents = highestBidCents as? Int { - return "\(prefix)\(NumberFormatter.currencyString(forCents: currentBidCents as NSNumber!))" + let formatted = NumberFormatter.currencyString(forDollarCents: currentBidCents as NSNumber) ?? "" + return "\(prefix)\(formatted)" } else { - return "\(missingPrefix)\(NumberFormatter.currencyString(forCents: self?.saleArtwork.openingBidCents ?? 0))" + let formatted = NumberFormatter.currencyString(forDollarCents: self?.saleArtwork.openingBidCents ?? 0) ?? "" + return "\(missingPrefix)\(formatted)" } } } diff --git a/Kiosk/Bid Fulfillment/BidDetailsPreviewView.swift b/Kiosk/Bid Fulfillment/BidDetailsPreviewView.swift index df248b7e..dc4f9544 100644 --- a/Kiosk/Bid Fulfillment/BidDetailsPreviewView.swift +++ b/Kiosk/Bid Fulfillment/BidDetailsPreviewView.swift @@ -80,7 +80,7 @@ class BidDetailsPreviewView: UIView { .map { bidDetails in guard let cents = bidDetails.bidAmountCents.value else { return "" } - return "Your bid: " + NumberFormatter.currencyString(forCents: cents) + return "Your bid: " + NumberFormatter.currencyString(forDollarCents: cents) ?? "" } .bindTo(currentBidPriceLabel.rx.text) .addDisposableTo(rx_disposeBag) diff --git a/Kiosk/Bid Fulfillment/ConfirmYourBidViewController.swift b/Kiosk/Bid Fulfillment/ConfirmYourBidViewController.swift index 0ef0c3fb..8a7c3f7e 100644 --- a/Kiosk/Bid Fulfillment/ConfirmYourBidViewController.swift +++ b/Kiosk/Bid Fulfillment/ConfirmYourBidViewController.swift @@ -124,7 +124,7 @@ class ConfirmYourBidViewController: UIViewController { } func toOpeningBidString(_ cents:AnyObject!) -> AnyObject! { - if let dollars = NumberFormatter.currencyString(forCents: cents as? Int as NSNumber!) { + if let dollars = NumberFormatter.currencyString(forDollarCents: cents as? Int as NSNumber!) { return "Enter \(dollars) or more" as AnyObject! } return "" as AnyObject! diff --git a/Kiosk/Bid Fulfillment/PlaceBidViewController.swift b/Kiosk/Bid Fulfillment/PlaceBidViewController.swift index c81ce611..cc96fc1b 100644 --- a/Kiosk/Bid Fulfillment/PlaceBidViewController.swift +++ b/Kiosk/Bid Fulfillment/PlaceBidViewController.swift @@ -278,7 +278,7 @@ func dollarsToCurrencyString(_ dollars: Int) -> String { } func toNextBidString(_ cents: Int) -> String { - guard let dollars = NumberFormatter.currencyString(forCents: cents as NSNumber!) else { + guard let dollars = NumberFormatter.currencyString(forDollarCents: cents as NSNumber!) else { return "" } return "Enter \(dollars) or more" diff --git a/Kiosk/HelperFunctions.swift b/Kiosk/HelperFunctions.swift index b69fe6fe..87d74eb7 100644 --- a/Kiosk/HelperFunctions.swift +++ b/Kiosk/HelperFunctions.swift @@ -9,7 +9,7 @@ func stringIsEmailAddress(_ text: String) -> Bool { } func centsToPresentableDollarsString(_ cents: Int) -> String { - guard let dollars = NumberFormatter.currencyString(forCents: cents as NSNumber!) else { + guard let dollars = NumberFormatter.currencyString(forDollarCents: cents as NSNumber!) else { return "" } diff --git a/KioskTests/ReferenceImages/HelpViewControllerTests/a_help_view_controller__looks_correct@2x.png b/KioskTests/ReferenceImages/HelpViewControllerTests/a_help_view_controller__looks_correct@2x.png index 0387f04663537964df8571046598edbb2e9d6b35..52c12397ca176443542ead5bde119c6292c19c34 100644 GIT binary patch delta 93535 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 delta 92939 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