diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b13ba39859..304db4b0d20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,10 @@ * Minimum supported Xcode version is now 9.0 * `AddressBook` framework support has been removed. * `STPRedirectContext` will no longer retain itself for the duration of the redirect, you must explicitly maintain a reference to it yourself. -* `STPPaymentConfiguration.requiredShippingAddress` now is a set of `STPContactField` objects instead of a `PKAddressField` bitmask. +* `STPPaymentConfiguration.requiredShippingAddress` now is a set of `STPContactField` objects instead of a `PKAddressField` bitmask. * See MIGRATING.md for more information on any of the previously mentioned breaking API changes. * Pre-built view controllers now layout properly on iPhone X in landscape orientation, respecting `safeAreaInsets`. - +* `STPPaymentContext` now has a `largeTitleDisplayMode` property, which you can use to control the title display mode in the navigation bar of our pre-built view controllers. ## 11.5.0 2017-11-09 * Adds a new helper method to `STPSourceParams` for creating reusable Alipay sources. [#811](https://github.com/stripe/stripe-ios/pull/811) diff --git a/Example/Standard Integration (Swift)/BrowseProductsViewController.swift b/Example/Standard Integration (Swift)/BrowseProductsViewController.swift index 9c561871d4d..dbdec331120 100644 --- a/Example/Standard Integration (Swift)/BrowseProductsViewController.swift +++ b/Example/Standard Integration (Swift)/BrowseProductsViewController.swift @@ -35,6 +35,7 @@ class BrowseProductsViewController: UITableViewController { super.viewDidLoad() self.navigationItem.title = "Emoji Apparel" self.navigationController?.navigationBar.isTranslucent = false + self.navigationController?.view.backgroundColor = .white self.navigationItem.backBarButtonItem = UIBarButtonItem(title: "Products", style: .plain, target: nil, action: nil) self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Settings", style: .plain, target: self, action: #selector(showSettings)) } diff --git a/Stripe.xcodeproj/project.pbxproj b/Stripe.xcodeproj/project.pbxproj index cbcc7d9a6ea..1661f61aaf6 100644 --- a/Stripe.xcodeproj/project.pbxproj +++ b/Stripe.xcodeproj/project.pbxproj @@ -349,6 +349,7 @@ 8BD87B931EFB1C1E00269C2B /* STPSourceVerification+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 8BD87B911EFB1C1E00269C2B /* STPSourceVerification+Private.h */; }; 8BD87B951EFB1CB100269C2B /* STPSourceVerificationTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BD87B941EFB1CB100269C2B /* STPSourceVerificationTest.m */; }; 8BE5AE8B1EF8905B0081A33C /* STPCardParamsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BE5AE8A1EF8905B0081A33C /* STPCardParamsTest.m */; }; + C1054F911FE197AE0033C87E /* STPPaymentContextSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = C1054F901FE197AE0033C87E /* STPPaymentContextSnapshotTests.m */; }; C1080F491CBECF7B007B2D89 /* STPAddress.h in Headers */ = {isa = PBXBuildFile; fileRef = C1080F471CBECF7B007B2D89 /* STPAddress.h */; settings = {ATTRIBUTES = (Public, ); }; }; C1080F4A1CBECF7B007B2D89 /* STPAddress.m in Sources */ = {isa = PBXBuildFile; fileRef = C1080F481CBECF7B007B2D89 /* STPAddress.m */; }; C1080F4C1CBED48A007B2D89 /* STPAddressTests.m in Sources */ = {isa = PBXBuildFile; fileRef = C1080F4B1CBED48A007B2D89 /* STPAddressTests.m */; }; @@ -993,6 +994,7 @@ 8BD87B911EFB1C1E00269C2B /* STPSourceVerification+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "STPSourceVerification+Private.h"; sourceTree = ""; }; 8BD87B941EFB1CB100269C2B /* STPSourceVerificationTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = STPSourceVerificationTest.m; sourceTree = ""; }; 8BE5AE8A1EF8905B0081A33C /* STPCardParamsTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = STPCardParamsTest.m; sourceTree = ""; }; + C1054F901FE197AE0033C87E /* STPPaymentContextSnapshotTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STPPaymentContextSnapshotTests.m; sourceTree = ""; }; C1080F471CBECF7B007B2D89 /* STPAddress.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = STPAddress.h; path = PublicHeaders/STPAddress.h; sourceTree = ""; }; C1080F481CBECF7B007B2D89 /* STPAddress.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = STPAddress.m; sourceTree = ""; }; C1080F4B1CBED48A007B2D89 /* STPAddressTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = STPAddressTests.m; sourceTree = ""; }; @@ -1485,6 +1487,7 @@ F1B980931DB550E60075332E /* STPPaymentMethodsViewControllerLocalizationTests.m */, C1EF04491DD2396200FBF452 /* STPShippingAddressViewControllerLocalizationTests.m */, C1EF044A1DD2396200FBF452 /* STPShippingMethodsViewControllerLocalizationTests.m */, + C1054F901FE197AE0033C87E /* STPPaymentContextSnapshotTests.m */, ); name = Snapshot; sourceTree = ""; @@ -2646,6 +2649,7 @@ F148ABFB1D5E88C70014FD92 /* STPTestUtils.m in Sources */, 8BB97F081F26645B0095122A /* NSDictionary+StripeTest.m in Sources */, 045A62AB1B8E7259000165CE /* STPPaymentCardTextFieldTest.m in Sources */, + C1054F911FE197AE0033C87E /* STPPaymentContextSnapshotTests.m in Sources */, C127110A1DBA7E490087840D /* STPAddressViewModelTest.m in Sources */, C17D24EE1E37DBAC005CB188 /* STPSourceTest.m in Sources */, C1E4F8061EBBEB0F00E611F5 /* STPCustomerContextTest.m in Sources */, diff --git a/Stripe/PublicHeaders/STPPaymentContext.h b/Stripe/PublicHeaders/STPPaymentContext.h index 2a5868ff3e9..a95db56e2da 100644 --- a/Stripe/PublicHeaders/STPPaymentContext.h +++ b/Stripe/PublicHeaders/STPPaymentContext.h @@ -233,6 +233,21 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, assign) UIModalPresentationStyle modalPresentationStyle; +/** + The mode to use when displaying the title of the navigation bar in all view + controllers presented by the context. The default value is `automatic`, + which causes the title to use the same styling as the previously displayed + navigation item (if the view controller is pushed onto the `hostViewController`). + + If the `prefersLargeTitles` property of the `hostViewController`'s navigation bar + is false, this property has no effect and the navigation item's title is always + displayed as a small title. + + If the view controller is presented modally, `automatic` and + `never` always result in a navigation bar with a small title. + */ +@property (nonatomic, assign) UINavigationItemLargeTitleDisplayMode largeTitleDisplayMode NS_AVAILABLE_IOS(11_0); + /** A view that will be placed as the footer of the payment methods selection view controller. diff --git a/Stripe/STPPaymentContext.m b/Stripe/STPPaymentContext.m index 3853bc30ecc..039308dce39 100644 --- a/Stripe/STPPaymentContext.m +++ b/Stripe/STPPaymentContext.m @@ -102,6 +102,9 @@ - (instancetype)initWithAPIAdapter:(id)apiAdapter _paymentCountry = @"US"; _paymentAmountModel = [[STPPaymentContextAmountModel alloc] initWithAmount:0]; _modalPresentationStyle = UIModalPresentationFullScreen; + if (@available(iOS 11, *)) { + _largeTitleDisplayMode = UINavigationItemLargeTitleDisplayModeAutomatic; + } _state = STPPaymentContextStateNone; [self retryLoading]; } @@ -157,6 +160,11 @@ - (BOOL)loading { return !self.loadingPromise.completed; } +// Disable transition animations in tests +- (BOOL)transitionAnimationsEnabled { + return NSClassFromString(@"XCTest") == nil; +} + - (void)setHostViewController:(UIViewController *)hostViewController { NSCAssert(_hostViewController == nil, @"You cannot change the hostViewController on an STPPaymentContext after it's already been set."); _hostViewController = hostViewController; @@ -287,11 +295,19 @@ - (void)presentPaymentMethodsViewControllerWithNewState:(STPPaymentContextState) paymentMethodsViewController.prefilledInformation = self.prefilledInformation; paymentMethodsViewController.paymentMethodsViewControllerFooterView = self.paymentMethodsViewControllerFooterView; paymentMethodsViewController.addCardViewControllerFooterView = self.addCardViewControllerFooterView; + if (@available(iOS 11, *)) { + paymentMethodsViewController.navigationItem.largeTitleDisplayMode = self.largeTitleDisplayMode; + } UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:paymentMethodsViewController]; navigationController.navigationBar.stp_theme = self.theme; + if (@available(iOS 11, *)) { + navigationController.navigationBar.prefersLargeTitles = YES; + } navigationController.modalPresentationStyle = self.modalPresentationStyle; - [self.hostViewController presentViewController:navigationController animated:YES completion:nil]; + [self.hostViewController presentViewController:navigationController + animated:[self transitionAnimationsEnabled] + completion:nil]; } }]; } @@ -316,8 +332,12 @@ - (void)pushPaymentMethodsViewController { paymentMethodsViewController.prefilledInformation = self.prefilledInformation; paymentMethodsViewController.paymentMethodsViewControllerFooterView = self.paymentMethodsViewControllerFooterView; paymentMethodsViewController.addCardViewControllerFooterView = self.addCardViewControllerFooterView; - - [navigationController pushViewController:paymentMethodsViewController animated:YES]; + if (@available(iOS 11, *)) { + paymentMethodsViewController.navigationItem.largeTitleDisplayMode = self.largeTitleDisplayMode; + } + + [navigationController pushViewController:paymentMethodsViewController + animated:[self transitionAnimationsEnabled]]; } }]; } @@ -360,7 +380,8 @@ - (void)appropriatelyDismissPaymentMethodsViewController:(STPPaymentMethodsViewC completion:(STPVoidBlock)completion { if ([viewController stp_isAtRootOfNavigationController]) { // if we're the root of the navigation controller, we've been presented modally. - [viewController.presentingViewController dismissViewControllerAnimated:YES completion:^{ + [viewController.presentingViewController dismissViewControllerAnimated:[self transitionAnimationsEnabled] + completion:^{ self.paymentMethodsViewController = nil; if (completion) { completion(); @@ -373,7 +394,9 @@ - (void)appropriatelyDismissPaymentMethodsViewController:(STPPaymentMethodsViewC if ([self.hostViewController isKindOfClass:[UINavigationController class]]) { destinationViewController = self.originalTopViewController; } - [viewController.navigationController stp_popToViewController:destinationViewController animated:YES completion:^{ + [viewController.navigationController stp_popToViewController:destinationViewController + animated:[self transitionAnimationsEnabled] + completion:^{ self.paymentMethodsViewController = nil; if (completion) { completion(); @@ -397,10 +420,18 @@ - (void)presentShippingViewControllerWithNewState:(STPPaymentContextState)state self.state = state; STPShippingAddressViewController *addressViewController = [[STPShippingAddressViewController alloc] initWithPaymentContext:self]; + if (@available(iOS 11, *)) { + addressViewController.navigationItem.largeTitleDisplayMode = self.largeTitleDisplayMode; + } UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:addressViewController]; navigationController.navigationBar.stp_theme = self.theme; + if (@available(iOS 11, *)) { + navigationController.navigationBar.prefersLargeTitles = YES; + } navigationController.modalPresentationStyle = self.modalPresentationStyle; - [self.hostViewController presentViewController:navigationController animated:YES completion:nil]; + [self.hostViewController presentViewController:navigationController + animated:[self transitionAnimationsEnabled] + completion:nil]; } }]; } @@ -421,7 +452,11 @@ - (void)pushShippingViewController { self.state = STPPaymentContextStateShowingRequestedViewController; STPShippingAddressViewController *addressViewController = [[STPShippingAddressViewController alloc] initWithPaymentContext:self]; - [navigationController pushViewController:addressViewController animated:YES]; + if (@available(iOS 11, *)) { + addressViewController.navigationItem.largeTitleDisplayMode = self.largeTitleDisplayMode; + } + [navigationController pushViewController:addressViewController + animated:[self transitionAnimationsEnabled]]; } }]; } @@ -480,7 +515,8 @@ - (void)appropriatelyDismissViewController:(UIViewController *)viewController completion:(STPVoidBlock)completion { if ([viewController stp_isAtRootOfNavigationController]) { // if we're the root of the navigation controller, we've been presented modally. - [viewController.presentingViewController dismissViewControllerAnimated:YES completion:^{ + [viewController.presentingViewController dismissViewControllerAnimated:[self transitionAnimationsEnabled] + completion:^{ if (completion) { completion(); } @@ -492,7 +528,9 @@ - (void)appropriatelyDismissViewController:(UIViewController *)viewController if ([self.hostViewController isKindOfClass:[UINavigationController class]]) { destinationViewController = self.originalTopViewController; } - [viewController.navigationController stp_popToViewController:destinationViewController animated:YES completion:^{ + [viewController.navigationController stp_popToViewController:destinationViewController + animated:[self transitionAnimationsEnabled] + completion:^{ if (completion) { completion(); } @@ -613,13 +651,14 @@ - (void)requestPayment { onPaymentAuthorization:paymentHandler onTokenCreation:applePayTokenHandler onFinish:^(STPPaymentStatus status, NSError * _Nullable error) { - [self.hostViewController dismissViewControllerAnimated:YES completion:^{ + [self.hostViewController dismissViewControllerAnimated:[self transitionAnimationsEnabled] + completion:^{ [self didFinishWithStatus:status error:error]; }]; }]; [self.hostViewController presentViewController:paymentAuthVC - animated:YES + animated:[self transitionAnimationsEnabled] completion:nil]; } }] onFailure:^(NSError *error) { diff --git a/Tests/ReferenceImages_64/STPPaymentContextSnapshotTests/testPushPaymentMethodsLargeTitle@2x.png b/Tests/ReferenceImages_64/STPPaymentContextSnapshotTests/testPushPaymentMethodsLargeTitle@2x.png new file mode 100644 index 00000000000..e7daddb263d Binary files /dev/null and b/Tests/ReferenceImages_64/STPPaymentContextSnapshotTests/testPushPaymentMethodsLargeTitle@2x.png differ diff --git a/Tests/ReferenceImages_64/STPPaymentContextSnapshotTests/testPushPaymentMethodsSmallTitle@2x.png b/Tests/ReferenceImages_64/STPPaymentContextSnapshotTests/testPushPaymentMethodsSmallTitle@2x.png new file mode 100644 index 00000000000..10f8cda7fda Binary files /dev/null and b/Tests/ReferenceImages_64/STPPaymentContextSnapshotTests/testPushPaymentMethodsSmallTitle@2x.png differ diff --git a/Tests/ReferenceImages_64/STPPaymentContextSnapshotTests/testPushShippingAddressLargeTitle@2x.png b/Tests/ReferenceImages_64/STPPaymentContextSnapshotTests/testPushShippingAddressLargeTitle@2x.png new file mode 100644 index 00000000000..6f965837335 Binary files /dev/null and b/Tests/ReferenceImages_64/STPPaymentContextSnapshotTests/testPushShippingAddressLargeTitle@2x.png differ diff --git a/Tests/ReferenceImages_64/STPPaymentContextSnapshotTests/testPushShippingAddressSmallTitle@2x.png b/Tests/ReferenceImages_64/STPPaymentContextSnapshotTests/testPushShippingAddressSmallTitle@2x.png new file mode 100644 index 00000000000..8925fa689ca Binary files /dev/null and b/Tests/ReferenceImages_64/STPPaymentContextSnapshotTests/testPushShippingAddressSmallTitle@2x.png differ diff --git a/Tests/Tests/FBSnapshotTestCase+STPViewControllerLoading.h b/Tests/Tests/FBSnapshotTestCase+STPViewControllerLoading.h index 08bc35bdf84..a947f7ce1fd 100644 --- a/Tests/Tests/FBSnapshotTestCase+STPViewControllerLoading.h +++ b/Tests/Tests/FBSnapshotTestCase+STPViewControllerLoading.h @@ -9,5 +9,24 @@ #import @interface FBSnapshotTestCase (STPViewControllerLoading) + +/** + Embeds the given controller in a navigation controller, prepares it for + snapshot testing and returns the view controller's view. + */ - (UIView *)stp_preparedAndSizedViewForSnapshotTestFromViewController:(UIViewController *)viewController; + +/** + Returns a navigation controller initialized with the given root view controller + and prepares it for snapshot testing (adding it to a UIWindow and loading views) + */ +- (UINavigationController *)stp_navigationControllerForSnapshotTestWithRootVC:(UIViewController *)viewController; + +/** + Returns a view for snapshot testing from the topViewController of the given + navigation controller, making necessary layout adjustments for + `STPCoreScrollViewController`. + */ +- (UIView *)stp_preparedAndSizedViewForSnapshotTestFromNavigationController:(UINavigationController *)navController; + @end diff --git a/Tests/Tests/FBSnapshotTestCase+STPViewControllerLoading.m b/Tests/Tests/FBSnapshotTestCase+STPViewControllerLoading.m index fb5606158b1..cf37bab1528 100644 --- a/Tests/Tests/FBSnapshotTestCase+STPViewControllerLoading.m +++ b/Tests/Tests/FBSnapshotTestCase+STPViewControllerLoading.m @@ -10,13 +10,29 @@ #import "STPCoreScrollViewController+Private.h" @implementation FBSnapshotTestCase (STPViewControllerLoading) + - (UIView *)stp_preparedAndSizedViewForSnapshotTestFromViewController:(UIViewController *)viewController { + UINavigationController *navController = [self stp_navigationControllerForSnapshotTestWithRootVC:viewController]; + return [self stp_preparedAndSizedViewForSnapshotTestFromNavigationController:navController]; +} + +- (UINavigationController *)stp_navigationControllerForSnapshotTestWithRootVC:(UIViewController *)viewController { UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:viewController]; UIWindow *testWindow = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 320, 480)]; testWindow.rootViewController = navController; testWindow.hidden = NO; - // Test that loaded properly + loads them on first call + // Test that views loaded properly + loads them on first call + XCTAssertNotNil(navController.view); + XCTAssertNotNil(viewController.view); + + return navController; +} + +- (UIView *)stp_preparedAndSizedViewForSnapshotTestFromNavigationController:(UINavigationController *)navController { + UIViewController *viewController = [navController topViewController]; + + // Test that views loaded properly + loads them on first call XCTAssertNotNil(navController.view); XCTAssertNotNil(viewController.view); diff --git a/Tests/Tests/STPPaymentContextSnapshotTests.m b/Tests/Tests/STPPaymentContextSnapshotTests.m new file mode 100644 index 00000000000..02db3c39555 --- /dev/null +++ b/Tests/Tests/STPPaymentContextSnapshotTests.m @@ -0,0 +1,99 @@ +// +// STPPaymentContextSnapshotTests.m +// StripeiOS Tests +// +// Created by Ben Guo on 12/13/17. +// Copyright © 2017 Stripe, Inc. All rights reserved. +// + +#import +#import +#import + +#import "FBSnapshotTestCase+STPViewControllerLoading.h" +#import "STPFixtures.h" +#import "STPMocks.h" + +@interface STPPaymentContextSnapshotTests : FBSnapshotTestCase + +@property (nonatomic, strong) STPCustomerContext *customerContext; +@property (nonatomic, strong) STPPaymentConfiguration *config; +@property (nonatomic, strong) UINavigationController *hostViewController; +@property (nonatomic, strong) STPPaymentContext *paymentContext; + +@end + +@implementation STPPaymentContextSnapshotTests + +- (void)setUp { + [super setUp]; + STPPaymentConfiguration *config = [STPFixtures paymentConfiguration]; + config.companyName = @"Test Company"; + config.requiredBillingAddressFields = STPBillingAddressFieldsFull; + config.additionalPaymentMethods = STPPaymentMethodTypeAll; + config.shippingType = STPShippingTypeShipping; + self.config = config; + STPCustomerContext *customerContext = [STPMocks staticCustomerContextWithCustomer:[STPFixtures customerWithCardTokenAndSourceSources]]; + self.customerContext = customerContext; + + UIViewController *viewController = [UIViewController new]; + self.hostViewController = [self stp_navigationControllerForSnapshotTestWithRootVC:viewController]; + +// self.recordMode = YES; +} + +- (void)buildPaymentContext { + STPPaymentContext *context = [[STPPaymentContext alloc] initWithCustomerContext:self.customerContext]; + context.hostViewController = self.hostViewController; + self.paymentContext = context; +} + +- (void)testPushPaymentMethodsSmallTitle { + if (@available(iOS 11.0, *)) { + [self buildPaymentContext]; + + self.hostViewController.navigationBar.prefersLargeTitles = NO; + self.paymentContext.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayModeAutomatic; + [self.paymentContext pushPaymentMethodsViewController]; + UIView *view = [self stp_preparedAndSizedViewForSnapshotTestFromNavigationController:self.hostViewController]; + FBSnapshotVerifyView(view, nil); + } +} + +- (void)testPushPaymentMethodsLargeTitle { + if (@available(iOS 11.0, *)) { + [self buildPaymentContext]; + + self.hostViewController.navigationBar.prefersLargeTitles = YES; + self.paymentContext.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayModeAutomatic; + [self.paymentContext pushPaymentMethodsViewController]; + UIView *view = [self stp_preparedAndSizedViewForSnapshotTestFromNavigationController:self.hostViewController]; + FBSnapshotVerifyView(view, nil); + } +} + +- (void)testPushShippingAddressSmallTitle { + if (@available(iOS 11.0, *)) { + [self buildPaymentContext]; + + self.hostViewController.navigationBar.prefersLargeTitles = NO; + self.paymentContext.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayModeAutomatic; + [self.paymentContext pushShippingViewController]; + UIView *view = [self stp_preparedAndSizedViewForSnapshotTestFromNavigationController:self.hostViewController]; + FBSnapshotVerifyView(view, nil); + } +} + +- (void)testPushShippingAddressLargeTitle { + if (@available(iOS 11.0, *)) { + [self buildPaymentContext]; + + self.hostViewController.navigationBar.prefersLargeTitles = YES; + self.paymentContext.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayModeAutomatic; + [self.paymentContext pushShippingViewController]; + UIView *view = [self stp_preparedAndSizedViewForSnapshotTestFromNavigationController:self.hostViewController]; + FBSnapshotVerifyView(view, nil); + } +} + +@end