Skip to content

Commit

Permalink
Fix various layout issues with BottomSheetViewController (stripe#3298)
Browse files Browse the repository at this point in the history
## Summary
* The BlurView should present and dismiss with the sheet and cover the
unsafe area at the bottom.
* The presenter used to use a small background view at the bottom to
cover the gap between the keyboard and the sheet. Instead, the
BottomSheetViewController should extend to this area.
* When avoiding the keyboard, we were using a generic easeIn curve, so
there were sometimes gaps between the sheet and the keyboard. Use the
animation curve provided by the system instead.
* I'm not sure why the presentation controller was registering for
keyboard events — it makes more sense for the inner scrollview to avoid
the keyboard instead.
* Dismiss the BlurView when entering a 3DS2 challenge.
* Fix the keyboard calculation code on iPad. The FormSheet presenter
moves us around after the keyboard presents, so we need to calculate how
much of the keyboard to avoid based on the position *after* the
presenter moves us.

Before and after screenshots:
![CleanShot 2024-02-15 at 10 16
50](https://github.com/stripe/stripe-ios/assets/52758633/023653ee-799b-4f99-b8ab-a801808b327e)
![CleanShot 2024-02-15 at 10 17
13](https://github.com/stripe/stripe-ios/assets/52758633/597be5c1-7e4f-4158-9ed9-489ea99ebbf4)


## Motivation
Started by trying to fix the BlurView dismiss animation, then
hal-replacing-a-lightbulb.gif from there

## Testing
Existing snapshot tests and E2E UI tests.

## Changelog
None
  • Loading branch information
davidme-stripe authored Feb 16, 2024
1 parent 8fb6dfb commit 3eb298e
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 166 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -141,34 +141,20 @@ extension BottomSheetPresentationController {
containerView.addSubview(presentedView)

// We'll use this constraint to handle the keyboard
let bottomAnchor = presentedView.bottomAnchor.constraint(equalTo: containerView.safeAreaLayoutGuide.bottomAnchor)
let bottomAnchor = presentedView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
bottomAnchor.priority = .required
self.bottomAnchor = bottomAnchor

// Add a view between the bottom of the VC and the bottom of the screen for 2 reasons
// 1. The keyboard animation is sometimes erroneous and results in the bottom of the presented view decoupling from the top of the keyboard, exposing the view behind it.
// 2. The presented view (BottomSheetVC) does not inherit safeAreaLayoutGuide.bottom
let coverUpBottomView = UIView()
containerView.addSubview(coverUpBottomView)
coverUpBottomView.backgroundColor = presentedView.backgroundColor
coverUpBottomView.translatesAutoresizingMaskIntoConstraints = false

NSLayoutConstraint.activate([
presentedView.topAnchor.constraint(greaterThanOrEqualTo: containerView.safeAreaLayoutGuide.topAnchor),
presentedView.leadingAnchor.constraint(equalTo: containerView.safeAreaLayoutGuide.leadingAnchor),
presentedView.trailingAnchor.constraint(equalTo: containerView.safeAreaLayoutGuide.trailingAnchor),
bottomAnchor,

coverUpBottomView.topAnchor.constraint(equalTo: presentedView.bottomAnchor),
coverUpBottomView.leadingAnchor.constraint(equalTo: containerView.safeAreaLayoutGuide.leadingAnchor),
coverUpBottomView.trailingAnchor.constraint(equalTo: containerView.safeAreaLayoutGuide.trailingAnchor),
coverUpBottomView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
])

fullHeightConstraint.isActive = forceFullHeight

addRoundedCorners(to: presentedView)

registerForKeyboardNotifications()
}

// MARK: - Helpers
Expand All @@ -178,39 +164,4 @@ extension BottomSheetPresentationController {
view.layer.cornerRadius = 12
view.layer.masksToBounds = true
}

private func registerForKeyboardNotifications() {
NotificationCenter.default.addObserver(
self, selector: #selector(adjustForKeyboard),
name: UIResponder.keyboardWillHideNotification, object: nil)
NotificationCenter.default.addObserver(
self, selector: #selector(adjustForKeyboard),
name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
}

@objc
private func adjustForKeyboard(notification: Notification) {
guard
let keyboardScreenEndFrame =
(notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?
.cgRectValue,
let containerView = containerView,
let bottomAnchor = bottomAnchor
else {
return
}

let keyboardViewEndFrame = containerView.convert(keyboardScreenEndFrame, from: containerView.window)
let keyboardInViewHeight = containerView.bounds.intersection(keyboardViewEndFrame).height - containerView.safeAreaInsets.bottom
if notification.name == UIResponder.keyboardWillHideNotification {
bottomAnchor.constant = 0
} else {
bottomAnchor.constant = -keyboardInViewHeight
}

containerView.setNeedsLayout()
UIView.animateAlongsideKeyboard(notification) {
containerView.layoutIfNeeded()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ extension UIViewController {

if presentAsFormSheet {
viewControllerToPresent.modalPresentationStyle = .formSheet
// Don't allow the pull down to dismiss gesture, it's too easy to trigger accidentally while scrolling
viewControllerToPresent.isModalInPresentation = true
if let vc = viewControllerToPresent as? BottomSheetViewController {
viewControllerToPresent.presentationController?.delegate = vc
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ extension PaymentSheet: PaymentSheetViewControllerDelegate {
func paymentSheetViewControllerFinishedOnPay(_ paymentSheetViewController: PaymentSheetViewController,
completion: (() -> Void)? = nil) {
self.bottomSheetViewController.transitionSpinnerToComplete(animated: true) {
self.bottomSheetViewController.removeBlurEffect(animated: true, completion: completion)
completion?()
}
}

Expand Down
Loading

0 comments on commit 3eb298e

Please sign in to comment.