PKAddPassButton is moved to top of screen when call dismiss method of PKAddPaymentPassViewControllerDelegate

154 Views Asked by At

I have a problem. UiKitView is moved to top of screen when I call dismiss method of PKAddPaymentPassViewControllerDelegate class. Did you face with this problem ? When user click cancel button of Apple wallet app then wallet app is closed and the button is moved the top of screen. I want to keep PKAddPassButton on same place of screen after closing of apple wallet app

We use UIKitView to show PKAddPassButton in flutter as follow :

UiKitView(
viewType: viewType,
layoutDirection: TextDirection.ltr,
creationParams:
createParameters(state.panRefId),
creationParamsCodec:
const StandardMessageCodec(),
>                                     ),

We created UIViewController in swift to show it as follow :

import UIKit
import PassKit

class WalletButtonController: UIViewController {
    
    let cardInfo: AppleWalletBankCardInfo
    
    public init(cardInfo:AppleWalletBankCardInfo) {
        self.cardInfo = cardInfo
        super.init(nibName: nil, bundle: nil)
    }
   
    
    required public init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupApplePayButton()
    }

    private func setupApplePayButton() {
        let passKitButton = PKAddPassButton(addPassButtonStyle: .blackOutline)
        passKitButton.frame = CGRect(x: 100, y: 100, width: 200, height: 50)
        
        passKitButton.addTarget(self, action: #selector(onEnroll), for: .touchUpInside)
        view.addSubview(passKitButton)
        passKitButton.translatesAutoresizingMaskIntoConstraints = false
        passKitButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16).isActive = true
        passKitButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16).isActive = true
        passKitButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16).isActive = true

    }
    
    @objc private func onEnroll(button: UIButton) {
        guard isPassKitAvailable() else {
            showPassKitUnavailable(message: "InApp enrollment not available for this device")
            return
        }
        
        initEnrollProcess()
    }
    
    func initEnrollProcess() {
        
        guard let configuration = PKAddPaymentPassRequestConfiguration(encryptionScheme: .ECC_V2) else {
            showPassKitUnavailable(message: "InApp enrollment configuraton fails")
            return
        }
        
        configuration.cardholderName = cardInfo.bankCardHolder
        configuration.primaryAccountSuffix = cardInfo.bankCardNumber
        configuration.localizedDescription = cardInfo.bankCardName
        configuration.primaryAccountIdentifier = cardInfo.bankCardPanRefId
      
        guard let enrollViewController = PKAddPaymentPassViewController(requestConfiguration: configuration, delegate: self) else {
            showPassKitUnavailable(message: "InApp enrollment controller configuration fails")
            return
        }
        
        enrollViewController.modalPresentationStyle = UIModalPresentationStyle.pageSheet;
        present(enrollViewController, animated: true, completion: nil)
    }
    
    private func isPassKitAvailable() -> Bool {
        return PKAddPaymentPassViewController.canAddPaymentPass()
    }
    
    private func showPassKitUnavailable(message: String) {
        let alert = UIAlertController(title: "InApp Error",
                                      message: message,
                                      preferredStyle: .alert)
        let action = UIAlertAction(title: "Ok", style: .default, handler: nil)
        alert.addAction(action)
       
        present(alert, animated: true, completion: nil)
    }
}

extension WalletButtonController: PKAddPaymentPassViewControllerDelegate {
    func addPaymentPassViewController(
        _ controller: PKAddPaymentPassViewController,
        generateRequestWithCertificateChain certificates: [Data],
        nonce: Data, nonceSignature: Data,
        completionHandler handler: @escaping (PKAddPaymentPassRequest) -> Void) {
            
            let certArray = certificates.map { $0.base64EncodedString() }
            let request = IssuerRequest(certificates: certArray, nonce: nonce.base64EncodedString(), nonceSignature: nonceSignature.base64EncodedString(), bankCardId: cardInfo.bankCardId, accessToken: cardInfo.accessToken,encryptUrl: cardInfo.encryptUrl)
            
            let interactor = GetPassKitDataIssuerHostInteractor()
            interactor.execute(request: request) { response in
            
            let request = PKAddPaymentPassRequest()
                request.activationData = Data(base64Encoded: response.activationData, options: .ignoreUnknownCharacters)
                request.ephemeralPublicKey = Data(base64Encoded: response.ephemeralPublicKey, options: .ignoreUnknownCharacters)
                request.encryptedPassData = Data(base64Encoded: response.encryptedPassData, options: .ignoreUnknownCharacters)
                
                handler(request)
                
        }
    }
    
    func addPaymentPassViewController(
        _ controller: PKAddPaymentPassViewController,
        didFinishAdding pass: PKPaymentPass?,
        error: Error?) {
        if let _ = pass {
            print("Bank card was successfully added to wallet")
        } else {}
            self.dismiss(animated: true)
    }
    
}

private class GetPassKitDataIssuerHostInteractor {
    
    func execute(request: IssuerRequest, onComplete: @escaping(IssuerResponse) -> ()) {
       
        executeInternal(issuerRequest: request, completion:  {
            (response) in
            
                let responseData = try response.get()
                
                let response = IssuerResponse(activationData: responseData.activationData,
                                              ephemeralPublicKey: responseData.ephemeralPublicKey,
                                              encryptedPassData: responseData.encryptedPassData)
                onComplete(response)
        })
    }
    
    func executeInternal(issuerRequest: IssuerRequest ,completion: @escaping(Result<IssuerResponse, Error>) throws -> Void) {
        
        var request = URLRequest(url: URL(string:issuerRequest.encryptUrl)!)
        
        request.httpMethod = "POST"
        request.setValue("*/*", forHTTPHeaderField: "Accept")
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.setValue("Bearer \(issuerRequest.accessToken)", forHTTPHeaderField: "Authorization")
       
        let body:[String:Any]=[
            "bankCardId": "\(issuerRequest.bankCardId)",
            "nonce": "\(issuerRequest.nonce)",
            "nonceSignature": "\(issuerRequest.nonceSignature)",
            "certificates": issuerRequest.certificates
          ]
        
        request.httpBody = try? JSONSerialization.data(withJSONObject: body,options: [])
        
        URLSession.shared.dataTask(with: request) { (data, response, e) in
          
            if let httpResponse = response as? HTTPURLResponse{
                if httpResponse.statusCode >= 400 {
                    print(httpResponse.statusCode)
                    print(response)
                    return
                }
            }
     
            guard let data = data, e == nil else {
                print (e!.localizedDescription)
                return
            }
          
            do {
                let response = try JSONDecoder().decode(IssuerResponse.self, from: data)
                try completion(.success(response))
            }
            catch {
                print (error.localizedDescription)
                return
            }
        
        }.resume()
        }
}

struct IssuerRequest {
    let certificates: [String]
    let nonce: String
    let nonceSignature: String
    let bankCardId: String
    let accessToken: String
    let encryptUrl: String
}

struct IssuerResponse : Decodable {
    let activationData: String
    let ephemeralPublicKey: String
    let encryptedPassData: String
}

struct AppleWalletBankCardInfo
{
    let bankCardHolder: String
    let bankCardPanRefId: String
    let bankCardName: String
    let bankCardNumber: String
    let bankCardId: String
    let bankCardType: Int
    let accessToken: String
    let encryptUrl: String
    
    init(bankCardHolder: String, bankCardPanRefId: String, bankCardName: String, bankCardNumber: String,
     bankCardId: String, bankCardType: Int, accessToken: String,encryptUrl: String) {
        self.bankCardHolder = bankCardHolder
        self.bankCardPanRefId = bankCardPanRefId
        self.bankCardName = bankCardName
        self.bankCardNumber = bankCardNumber
        self.bankCardId = bankCardId
        self.bankCardType = bankCardType
        self.accessToken = accessToken
        self.encryptUrl = encryptUrl
    }
}
1

There are 1 best solutions below

0
Sebastijan Kokai On

I had the same issue and I resolved it by adding the UIViewController to the rootViewController:

let rootViewController = UIApplication.shared.windows.first?.rootViewController
rootViewController?.addChild(self)

So your setupApplePayButton() function could look something like this:

private func setupApplePayButton() {
        let rootViewController = UIApplication.shared.windows.first?.rootViewController
        rootViewController?.addChild(self)
        let passKitButton = PKAddPassButton(addPassButtonStyle: .blackOutline)
        passKitButton.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
        passKitButton.addTarget(self, action: #selector(onEnroll), for: .touchUpInside)
        view.addSubview(passKitButton)
    }

And then wrap your UiKitView inside your Flutter app with a SizedBox:

return SizedBox(
      height: 200,
      width: 200,
      child: UiKitView(
        viewType: viewType,
        layoutDirection: TextDirection.ltr,
        creationParams: creationParams,
        creationParamsCodec: const StandardMessageCodec(),
      ),
    );