Hey everyone! If you're searching for a painless and straight forward solution to your parallax issues, look no further! I've got a perfect, easy-to-digest solution for creating a parallax effect with a sticky header, a hide-able parallax view, and a scrollable container that activates once the parent scroll is finished.
The Problem:
Creating a parallax effect in React Native can be tricky, especially when trying to synchronize scrolling between a parent view and nested views. Common issues include the parallax view not hiding correctly, the child scroll view not activating at the right time, and the sticky header not behaving as expected.
The Solution:
The following code solves these problems by using a combination of React Native and react-native-reanimated to manage the scroll events efficiently. The solution ensures that the sticky header remains in place, the parallax view hides as the user scrolls up, and the nested scroll view becomes scrollable when the parent scroll is completed.
Here's the Code:
You can copy and paste the following code to get started:
import Foundation | |
import PassKit | |
import React | |
struct PaymentRequestParams: Codable { | |
let merchantIdentifier: String | |
let supportedNetworks: [String] | |
let countryCode: String | |
let currencyCode: String | |
let label: String | |
let amount: String | |
} | |
@objc(ApplePayModule) | |
class ApplePayModule: NSObject { | |
private var paymentResolver: RCTPromiseResolveBlock? | |
private var paymentRejecter: RCTPromiseRejectBlock? | |
private var completionHandler: ((PKPaymentAuthorizationResult) -> Void)? | |
@objc | |
static func requiresMainQueueSetup() -> Bool { | |
return true | |
} | |
@objc | |
func canMakePayments(_ resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock) { | |
resolver(PKPaymentAuthorizationController.canMakePayments()) | |
} | |
@objc | |
func requestPayment(_ paramsJSON: NSDictionary, resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) { | |
guard let jsonData = try? JSONSerialization.data(withJSONObject: paramsJSON, options: []), | |
let params = try? JSONDecoder().decode(PaymentRequestParams.self, from: jsonData) else { | |
rejecter("E_INVALID_PARAMS", "Invalid payment request parameters", nil) | |
return | |
} | |
let paymentRequest = PKPaymentRequest() | |
paymentRequest.merchantIdentifier = params.merchantIdentifier | |
paymentRequest.supportedNetworks = mapSupportedNetworks(params.supportedNetworks) | |
paymentRequest.merchantCapabilities = .capability3DS | |
paymentRequest.countryCode = params.countryCode | |
paymentRequest.currencyCode = params.currencyCode | |
paymentRequest.paymentSummaryItems = [ | |
PKPaymentSummaryItem(label: params.label, amount: NSDecimalNumber(string: params.amount)) | |
] | |
let paymentAuthorizationController = PKPaymentAuthorizationController(paymentRequest: paymentRequest) | |
paymentAuthorizationController.delegate = self | |
paymentAuthorizationController.present { (presented: Bool) in | |
if !presented { | |
rejecter("E_PAYMENT_ERROR", "Unable to present Apple Pay authorization.", nil) | |
} else { | |
self.paymentResolver = resolver | |
self.paymentRejecter = rejecter | |
} | |
} | |
} | |
@objc | |
func completePayment(_ success: Bool) { | |
guard let completionHandler = self.completionHandler else { | |
return | |
} | |
let status: PKPaymentAuthorizationStatus = success ? .success : .failure | |
let result = PKPaymentAuthorizationResult(status: status, errors: nil) | |
completionHandler(result) | |
self.completionHandler = nil | |
} | |
private func mapSupportedNetworks(_ networks: [String]) -> [PKPaymentNetwork] { | |
return networks.compactMap { network in | |
switch network.lowercased() { | |
case "visa": | |
return .visa | |
case "mastercard": | |
return .masterCard | |
case "mada": | |
return .mada | |
case "amex": | |
return .amex | |
case "discover": | |
return .discover | |
case "jcb": | |
return .JCB | |
case "maestro": | |
return .maestro | |
case "electron": | |
return .electron | |
case "vpay": | |
return .vPay | |
default: | |
return nil | |
} | |
} | |
} | |
} | |
extension ApplePayModule: PKPaymentAuthorizationControllerDelegate { | |
func paymentAuthorizationController(_ controller: PKPaymentAuthorizationController, didAuthorizePayment payment: PKPayment, handler completion: @escaping (PKPaymentAuthorizationResult) -> Void) { | |
// Store the completion handler | |
self.completionHandler = completion | |
// Handle the payment authorization | |
if let resolver = self.paymentResolver { | |
do { | |
let paymentData = try JSONSerialization.jsonObject(with: payment.token.paymentData, options: []) as? [String: Any] ?? [:] | |
resolver(["status": "success", "paymentData": paymentData]) | |
} catch { | |
if let rejecter = self.paymentRejecter { | |
rejecter("APPLE_PAY_PAYMENT_REJECTED", "Payment was rejected", error) | |
} | |
completion(PKPaymentAuthorizationResult(status: .failure, errors: nil)) | |
return | |
} | |
self.paymentResolver = nil | |
self.paymentRejecter = nil | |
} else { | |
// Resolver is nil, reject the payment | |
if let rejecter = self.paymentRejecter { | |
rejecter("APPLE_PAY_PAYMENT_REJECTED", "Payment was rejected", nil) | |
self.paymentResolver = nil | |
self.paymentRejecter = nil | |
} | |
completion(PKPaymentAuthorizationResult(status: .failure, errors: nil)) | |
} | |
} | |
func paymentAuthorizationControllerDidFinish(_ controller: PKPaymentAuthorizationController) { | |
controller.dismiss { | |
// Handle the dismissal of the payment sheet | |
if let rejecter = self.paymentRejecter { | |
rejecter("APPLE_PAY_PAYMENT_CANCELLED", "Payment was cancelled", nil) | |
self.paymentResolver = nil | |
self.paymentRejecter = nil | |
} | |
} | |
} | |
} |
How It Works:
Sticky Header: The header changes color based on the scroll position, remaining visible at the top of the screen until the user scrolls to the bottom of the parent view.
Parallax View: The parallax section hides as the user scrolls up, creating a smooth transition effect.
Scrollable Container: The nested scroll view becomes scrollable only after the parent scroll view reaches the bottom, ensuring a seamless user experience.
This solution effectively manages the scrolling behavior between the parent and child views, providing a smooth and visually appealing parallax effect. Feel free to use and modify the code as needed for your projects!
Happy coding! 🎉

Please consider sharing your experiences and improvements in the comments below. Let's keep the learning going!
Top comments (0)