diff --git a/Example/Standard Integration (Swift).xcodeproj/project.pbxproj b/Example/Standard Integration (Swift).xcodeproj/project.pbxproj index 833bc3aa913..eb4da49cbc2 100644 --- a/Example/Standard Integration (Swift).xcodeproj/project.pbxproj +++ b/Example/Standard Integration (Swift).xcodeproj/project.pbxproj @@ -19,6 +19,7 @@ C186AC341ECD0DDD00497DE3 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C186AC331ECD0DDD00497DE3 /* Alamofire.framework */; }; C188F2A61CC1A338003A524B /* MyAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = C188F2A51CC1A338003A524B /* MyAPIClient.swift */; }; C1B83CBD1CCE6C0700F0790B /* Buttons.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1B83CBC1CCE6C0700F0790B /* Buttons.swift */; }; + F1D12FE71F91721600CE68A7 /* PaymentContextFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D12FE61F91721600CE68A7 /* PaymentContextFooterView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -58,6 +59,7 @@ C188F2A51CC1A338003A524B /* MyAPIClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyAPIClient.swift; sourceTree = ""; }; C18A6EE11F1EB78B005600CC /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = "Standard Integration (Swift)/README.md"; sourceTree = ""; }; C1B83CBC1CCE6C0700F0790B /* Buttons.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Buttons.swift; sourceTree = ""; }; + F1D12FE61F91721600CE68A7 /* PaymentContextFooterView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentContextFooterView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -83,6 +85,7 @@ 04BC299F1CD81D3900318357 /* BrowseProductsViewController.swift */, C124A18C1CCACC92007D42EE /* CheckoutRowView.swift */, C1B83CBC1CCE6C0700F0790B /* Buttons.swift */, + F1D12FE61F91721600CE68A7 /* PaymentContextFooterView.swift */, 042CA4191A685E8D00D778E7 /* Images.xcassets */, 042CA41A1A685E8D00D778E7 /* Info.plist */, 04D075D91A69B82B00094431 /* Standard Integration (Swift).entitlements */, @@ -264,6 +267,7 @@ C1B83CBD1CCE6C0700F0790B /* Buttons.swift in Sources */, C124A18D1CCACC92007D42EE /* CheckoutRowView.swift in Sources */, 042CA41D1A685E8D00D778E7 /* AppDelegate.swift in Sources */, + F1D12FE71F91721600CE68A7 /* PaymentContextFooterView.swift in Sources */, C13C24221D14438700F3765E /* SettingsViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Example/Standard Integration (Swift)/CheckoutViewController.swift b/Example/Standard Integration (Swift)/CheckoutViewController.swift index e3d2c62146b..88b9c5e15cb 100644 --- a/Example/Standard Integration (Swift)/CheckoutViewController.swift +++ b/Example/Standard Integration (Swift)/CheckoutViewController.swift @@ -90,6 +90,15 @@ class CheckoutViewController: UIViewController, STPPaymentContextDelegate { paymentContext.prefilledInformation = userInformation paymentContext.paymentAmount = price paymentContext.paymentCurrency = self.paymentCurrency + + let paymentSelectionFooter = PaymentContextFooterView(text: "You can add custom footer views to the payment selection screen.") + paymentSelectionFooter.theme = settings.theme + paymentContext.paymentMethodsViewControllerFooterView = paymentSelectionFooter + + let addCardFooter = PaymentContextFooterView(text: "You can add custom footer views to the add card screen.") + addCardFooter.theme = settings.theme + paymentContext.addCardViewControllerFooterView = addCardFooter + self.paymentContext = paymentContext self.paymentRow = CheckoutRowView(title: "Payment", detail: "Select Payment", diff --git a/Example/Standard Integration (Swift)/PaymentContextFooterView.swift b/Example/Standard Integration (Swift)/PaymentContextFooterView.swift new file mode 100644 index 00000000000..957d237a39a --- /dev/null +++ b/Example/Standard Integration (Swift)/PaymentContextFooterView.swift @@ -0,0 +1,61 @@ +// +// PaymentContextFooterView.swift +// Standard Integration (Swift) +// +// Created by Brian Dorfman on 10/13/17. +// Copyright © 2017 Stripe. All rights reserved. +// + +import UIKit +import Stripe + +class PaymentContextFooterView: UIView { + + var insetMargins: UIEdgeInsets = UIEdgeInsetsMake(10, 10, 10, 10) + + var text: String = "" { + didSet { + textLabel.text = text + } + } + + var theme: STPTheme = STPTheme.default() { + didSet { + textLabel.font = theme.smallFont + textLabel.textColor = theme.secondaryForegroundColor + } + } + + fileprivate let textLabel = UILabel() + + convenience init(text: String) { + self.init() + textLabel.numberOfLines = 0 + textLabel.textAlignment = .center + self.addSubview(textLabel) + + self.text = text + textLabel.text = text + + } + + override func layoutSubviews() { + textLabel.frame = UIEdgeInsetsInsetRect(self.bounds, insetMargins) + } + + override func sizeThatFits(_ size: CGSize) -> CGSize { + // Add 10 pt border on all sides + var insetSize = size + insetSize.width -= (insetMargins.left + insetMargins.right) + insetSize.height -= (insetMargins.top + insetMargins.bottom) + + var newSize = textLabel.sizeThatFits(insetSize) + + newSize.width += (insetMargins.left + insetMargins.right) + newSize.height += (insetMargins.top + insetMargins.bottom) + + return newSize + } + + +} diff --git a/Stripe/PublicHeaders/STPAddCardViewController.h b/Stripe/PublicHeaders/STPAddCardViewController.h index 8f97774e3ae..bac5d6cc410 100644 --- a/Stripe/PublicHeaders/STPAddCardViewController.h +++ b/Stripe/PublicHeaders/STPAddCardViewController.h @@ -55,6 +55,15 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, copy, nullable) NSString *managedAccountCurrency; +/** + Provide this view controller with a footer view. + + When the footer view needs to be resized, it will be sent a + `sizeThatFits:` call. The view should respond correctly to this method in order + to be sized and positioned properly. + */ +@property (nonatomic, strong, nullable) UIView *customFooterView; + @end /** diff --git a/Stripe/PublicHeaders/STPPaymentContext.h b/Stripe/PublicHeaders/STPPaymentContext.h index 1a0f7c5dbc9..04d19793cfc 100644 --- a/Stripe/PublicHeaders/STPPaymentContext.h +++ b/Stripe/PublicHeaders/STPPaymentContext.h @@ -223,6 +223,25 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, assign) UIModalPresentationStyle modalPresentationStyle; +/** + A view that will be placed as the footer of the payment methods selection + view controller. + + When the footer view needs to be resized, it will be sent a + `sizeThatFits:` call. The view should respond correctly to this method in order + to be sized and positioned properly. + */ +@property (nonatomic, strong) UIView *paymentMethodsViewControllerFooterView; + +/** + A view that will be placed as the footer of the add card view controller. + + When the footer view needs to be resized, it will be sent a + `sizeThatFits:` call. The view should respond correctly to this method in order + to be sized and positioned properly. + */ +@property (nonatomic, strong) UIView *addCardViewControllerFooterView; + /** If `paymentContext:didFailToLoadWithError:` is called on your delegate, you can in turn call this method to try loading again (if that hasn't been called, @@ -288,7 +307,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)requestPayment; - @end /** diff --git a/Stripe/PublicHeaders/STPPaymentMethodsViewController.h b/Stripe/PublicHeaders/STPPaymentMethodsViewController.h index 8d2d4a76404..18b9b05754e 100644 --- a/Stripe/PublicHeaders/STPPaymentMethodsViewController.h +++ b/Stripe/PublicHeaders/STPPaymentMethodsViewController.h @@ -33,7 +33,6 @@ NS_ASSUME_NONNULL_BEGIN */ @interface STPPaymentMethodsViewController : STPCoreViewController - /** The delegate for the view controller. @@ -103,6 +102,26 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, strong, nullable) STPUserInformation *prefilledInformation; +/** + A view that will be placed as the footer of the view controller when it is + showing a list of saved payment methods to select from. + + When the footer view needs to be resized, it will be sent a + `sizeThatFits:` call. The view should respond correctly to this method in order + to be sized and positioned properly. + */ +@property (nonatomic, strong) UIView *paymentMethodsViewControllerFooterView; + +/** + A view that will be placed as the footer of the view controller when it is + showing the add card view. + + When the footer view needs to be resized, it will be sent a + `sizeThatFits:` call. The view should respond correctly to this method in order + to be sized and positioned properly. + */ +@property (nonatomic, strong) UIView *addCardViewControllerFooterView; + /** If you're pushing `STPPaymentMethodsViewController` onto an existing `UINavigationController`'s stack, you should use this method to dismiss it, diff --git a/Stripe/STPAddCardViewController.m b/Stripe/STPAddCardViewController.m index 946f6d66791..bf4519df93e 100644 --- a/Stripe/STPAddCardViewController.m +++ b/Stripe/STPAddCardViewController.m @@ -25,6 +25,7 @@ #import "STPPaymentConfiguration+Private.h" #import "STPPhoneNumberValidator.h" #import "STPPaymentCardTextFieldCell.h" +#import "STPPromise.h" #import "STPSectionHeaderView.h" #import "STPToken.h" #import "STPWeakStrongMacros.h" @@ -162,6 +163,17 @@ - (void)createAndSetupViews { [[STPAnalyticsClient sharedClient] clearAdditionalInfo]; } +- (void)viewDidLayoutSubviews { + [super viewDidLayoutSubviews]; + + // Resetting it re-calculates the size based on new view width + // UITableView requires us to call setter again to actually pick up frame + // change on footers + if (self.tableView.tableFooterView) { + self.customFooterView = self.tableView.tableFooterView; + } +} + - (void)setUpCardScanningIfAvailable { if ([STPCardIOProxy isCardIOAvailable]) { self.cardIOProxy = [[STPCardIOProxy alloc] initWithDelegate:self]; @@ -301,6 +313,16 @@ - (void)updateDoneButton { ); } +- (void)setCustomFooterView:(UIView *)footerView { + _customFooterView = footerView; + [self.stp_willAppearPromise voidOnSuccess:^{ + CGSize size = [footerView sizeThatFits:CGSizeMake(self.view.bounds.size.width, CGFLOAT_MAX)]; + footerView.frame = CGRectMake(0, 0, size.width, size.height); + + self.tableView.tableFooterView = footerView; + }]; +} + #pragma mark - STPPaymentCardTextField - (void)paymentCardTextFieldDidChange:(STPPaymentCardTextField *)textField { diff --git a/Stripe/STPPaymentContext.m b/Stripe/STPPaymentContext.m index ae3431ddca8..d499709eb64 100644 --- a/Stripe/STPPaymentContext.m +++ b/Stripe/STPPaymentContext.m @@ -306,6 +306,9 @@ - (void)presentPaymentMethodsViewControllerWithNewState:(STPPaymentContextState) STPPaymentMethodsViewController *paymentMethodsViewController = [[STPPaymentMethodsViewController alloc] initWithPaymentContext:self]; self.paymentMethodsViewController = paymentMethodsViewController; paymentMethodsViewController.prefilledInformation = self.prefilledInformation; + paymentMethodsViewController.paymentMethodsViewControllerFooterView = self.paymentMethodsViewControllerFooterView; + paymentMethodsViewController.addCardViewControllerFooterView = self.addCardViewControllerFooterView; + UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:paymentMethodsViewController]; navigationController.navigationBar.stp_theme = self.theme; navigationController.modalPresentationStyle = self.modalPresentationStyle; @@ -332,6 +335,9 @@ - (void)pushPaymentMethodsViewController { STPPaymentMethodsViewController *paymentMethodsViewController = [[STPPaymentMethodsViewController alloc] initWithPaymentContext:self]; self.paymentMethodsViewController = paymentMethodsViewController; paymentMethodsViewController.prefilledInformation = self.prefilledInformation; + paymentMethodsViewController.paymentMethodsViewControllerFooterView = self.paymentMethodsViewControllerFooterView; + paymentMethodsViewController.addCardViewControllerFooterView = self.addCardViewControllerFooterView; + [navigationController pushViewController:paymentMethodsViewController animated:YES]; } }]; diff --git a/Stripe/STPPaymentMethodsInternalViewController.h b/Stripe/STPPaymentMethodsInternalViewController.h index 9cfeadd2e98..078d4c5cca2 100644 --- a/Stripe/STPPaymentMethodsInternalViewController.h +++ b/Stripe/STPPaymentMethodsInternalViewController.h @@ -36,6 +36,9 @@ NS_ASSUME_NONNULL_BEGIN - (void)updateWithPaymentMethodTuple:(STPPaymentMethodTuple *)tuple; +@property (nonatomic, strong, nullable) UIView *customFooterView; + + @end NS_ASSUME_NONNULL_END diff --git a/Stripe/STPPaymentMethodsInternalViewController.m b/Stripe/STPPaymentMethodsInternalViewController.m index 5c11df72517..a26eb0e6d2d 100644 --- a/Stripe/STPPaymentMethodsInternalViewController.m +++ b/Stripe/STPPaymentMethodsInternalViewController.m @@ -20,9 +20,11 @@ #import "STPLocalizationUtils.h" #import "STPPaymentMethodTableViewCell.h" #import "STPPaymentMethodTuple.h" +#import "STPPromise.h" #import "STPSourceProtocol.h" #import "UITableViewCell+Stripe_Borders.h" #import "UIViewController+Stripe_NavigationItemProxy.h" +#import "UIViewController+Stripe_Promises.h" static NSString * const PaymentMethodCellReuseIdentifier = @"PaymentMethodCellReuseIdentifier"; @@ -91,6 +93,17 @@ - (void)createAndSetupViews { [self reloadRightBarButtonItemWithTableViewIsEditing:self.tableView.isEditing animated:NO]; } +- (void)viewDidLayoutSubviews { + [super viewDidLayoutSubviews]; + + // Resetting it re-calculates the size based on new view width + // UITableView requires us to call setter again to actually pick up frame + // change on footers + if (self.tableView.tableFooterView) { + self.customFooterView = self.tableView.tableFooterView; + } +} + - (void)reloadRightBarButtonItemWithTableViewIsEditing:(BOOL)tableViewIsEditing animated:(BOOL)animated { UIBarButtonItem *barButtonItem; @@ -166,6 +179,16 @@ - (void)updateWithPaymentMethodTuple:(STPPaymentMethodTuple *)tuple { [self.tableView reloadSections:sections withRowAnimation:UITableViewRowAnimationAutomatic]; } +- (void)setCustomFooterView:(UIView *)footerView { + _customFooterView = footerView; + [self.stp_willAppearPromise voidOnSuccess:^{ + CGSize size = [footerView sizeThatFits:CGSizeMake(self.view.bounds.size.width, CGFLOAT_MAX)]; + footerView.frame = CGRectMake(0, 0, size.width, size.height); + + self.tableView.tableFooterView = footerView; + }]; +} + #pragma mark - Button Handlers - (void)handleBackOrCancelTapped:(__unused id)sender { diff --git a/Stripe/STPPaymentMethodsViewController.m b/Stripe/STPPaymentMethodsViewController.m index 83d8ff8e2f3..cbf6878c81f 100644 --- a/Stripe/STPPaymentMethodsViewController.m +++ b/Stripe/STPPaymentMethodsViewController.m @@ -128,21 +128,31 @@ - (void)createAndSetupViews { if (tuple.paymentMethods.count > 0) { STPCustomerContext *customerContext = ([self.apiAdapter isKindOfClass:[STPCustomerContext class]]) ? (STPCustomerContext *)self.apiAdapter : nil; - internal = [[STPPaymentMethodsInternalViewController alloc] initWithConfiguration:self.configuration - customerContext:customerContext - theme:self.theme - prefilledInformation:self.prefilledInformation - shippingAddress:self.shippingAddress - paymentMethodTuple:tuple - delegate:self]; - } else { + STPPaymentMethodsInternalViewController *payMethodsInternal = [[STPPaymentMethodsInternalViewController alloc] initWithConfiguration:self.configuration + customerContext:customerContext + theme:self.theme + prefilledInformation:self.prefilledInformation + shippingAddress:self.shippingAddress + paymentMethodTuple:tuple + delegate:self]; + if (self.paymentMethodsViewControllerFooterView) { + payMethodsInternal.customFooterView = self.paymentMethodsViewControllerFooterView; + } + internal = payMethodsInternal; + } + else { STPAddCardViewController *addCardViewController = [[STPAddCardViewController alloc] initWithConfiguration:self.configuration theme:self.theme]; addCardViewController.delegate = self; addCardViewController.prefilledInformation = self.prefilledInformation; addCardViewController.shippingAddress = self.shippingAddress; internal = addCardViewController; - + + if (self.addCardViewControllerFooterView) { + addCardViewController.customFooterView = self.addCardViewControllerFooterView; + + } } + internal.stp_navigationItemProxy = self.navigationItem; [self addChildViewController:internal]; internal.view.alpha = 0;