# iOS QR Scan SDK
source: https://developer.mastercard.com/mastercard-merchant-presented-qr/documentation/device-sdks/ios-qr-scan-sdk/index.md

The iOS QR Scan SDK is primarliy used by Originating Institutions and Wallet Providers. It provides UI components for QR scanning that enable you to modify simple attributes of the views or use custom views for display.

This SDK is developed in Objective-C and it works with Swift. It is based on [QRCodeReaderViewController](https://github.com/yannickl/QRCodeReaderViewController).

## Requirements {#requirements}

* Xcode 9.0+
* iOS 8.0+

## Features {#features}

* Simple interface for QR scanning UI customization
* Easily extensible by using custom UI
* Scanning features for both portrait and landscape mode

## Installation {#installation}

### Swift {#swift}

1. Download the latest release of [MPQR Scan SDK](https://static.developer.mastercard.com/content/mastercard-merchant-presented-qr/sdk-files/mpqrscansdk.zip).
2. Unzip the file and place its contents in the project folder.
3. Go to your Xcode project's General settings. Add **MPQRScanSDK.xcframework** in Frameworks, Libraries and Embedded content.

### Objective-C {#objective-c}

Follow the same instructions as Swift.

## Usage {#usage}

In iOS10+, you must first provide reasoning for the camera use, and so you will need to add the **Privacy - Camera Usage Description** (`NSCameraUsageDescription`) field in your **Info.plist**.

### Simple {#simple}

1. Check camera permissions. Make sure use of the camera is authorized.
2. Create and configure `QRReaderViewControllerBuilder` instance.
3. Create a `QRReaderViewController` with `QRReaderViewControllerBuilder` instance.
4. Set the delegate of `QRReaderViewController` instance.
5. Present the controller.

**NOTE:** You should check whether the device supports the reader library by using the `QRCodeReader.isAvailable()` and the `QRCodeReader.supportsQRCode()` methods.
* Swift
* Objective-C

```Swift
import MPQRScanSDK
import AVFoundation

@IBAction func scanWithOriginalTheme(_ sender: Any) {
    guard QRCodeReader.isAvailable() && QRCodeReader.supportsQRCode() else {
        return
    }
    // Presents the readerVC
    checkCameraPermission { [weak self] in
        guard let strongSelf = self else {
            return
        }

        var reader:QRCodeReader?

        let qrVC = QRCodeReaderViewController(builder: QRCodeReaderViewControllerBuilder {
            $0.startScanningAtLoad = false
            reader = $0.reader
        })

        //block to read the result
        reader?.setCompletionWith({ result in
            reader?.stopScanning()
            self?.dismiss(animated: true, completion: nil)
        })

        //block when cancel is pressed
        qrVC.setCompletionWith({ result in
            reader?.stopScanning()
            self?.dismiss(animated: true, completion: nil)
        })

        // Retrieve the QRCode content via delegate
        qrVC.delegate = strongSelf

        strongSelf.present(qrVC, animated: true, completion: {
            qrVC.startScanning()
        })
    }
}

// Check camera permissions
func checkCameraPermission(completion: @escaping () -> Void) {
    let cameraMediaType = AVMediaType.video
    let cameraAuthorizationStatus = AVCaptureDevice.authorizationStatus(for: cameraMediaType)

    switch cameraAuthorizationStatus {
    case .denied:
        showAlert(title: "Error", message: "Camera permissions are required for scanning QR. Please turn on Settings -> MasterpassQR Demo -> Camera")
        break
    case .restricted:
        showAlert(title: "Error", message: "Camera permissions are restricted for scanning QR")
        break
    case .authorized:
        completion()
    case .notDetermined:
        // Prompting user for the permission to use the camera.
        AVCaptureDevice.requestAccess(for: cameraMediaType) { [weak self] granted in
            guard let strongSelf = self else { return }

            DispatchQueue.main.async {
                if granted {
                    completion()
                } else {
                    strongSelf.showAlert(title: "Error", message: "Camera permissions are required for scanning QR. Please turn on Settings -> MasterpassQR Demo -> Camera")
                }
            }
        }
    }
}

func showAlert(title: String, message: String) {
    let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
    alert.addAction(UIAlertAction(title: "Ok", style: .cancel, handler: nil))
    present(alert, animated: true, completion: nil)
}

```

```Objective-C
@import MPQRScanSDK;
@import AVFoundation;

- (IBAction)scanAction:(id)sender {
  if (![QRCodeReader isAvailable] || ![QRCodeReader supportsQRCode]) {
      return;
  }
  __weak typeof(self) weakSelf = self;
  [self checkCameraPermission: ^{

      __block __weak QRCodeReader* reader;
      QRCodeReaderViewController* qrVC = [QRCodeReaderViewController readerWithBuilderBlock:^(QRCodeReaderViewControllerBuilder *builder){
          reader = builder.reader;
      }];

      //block to read the result
      [reader setCompletionWithBlock:^(NSString *result) {
          [reader stopScanning];
          [self dismissViewControllerAnimated:YES completion: nil];
      }];

      //block when cancel is pressed
      [qrVC setCompletionWithBlock:^(NSString *result) {
          [reader stopScanning];
          [self dismissViewControllerAnimated:YES completion: nil];
      }];

      // Retrieve the QRCode content via delegate
      qrVC.delegate = weakSelf;

      [weakSelf presentViewController:qrVC animated:true completion:nil];
  }];
}

// Check camera permissions
- (void)checkCameraPermission:(void (^)(void))completion {
    AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
    if (status == AVAuthorizationStatusDenied) {
        [self showAlertWithTitle:@"Error" message: @"Camera permissions are required for scanning QR. Please turn on Settings -> MPQR Demo -> Camera"];
        return;
    } else if (status == AVAuthorizationStatusRestricted) {
        [self showAlertWithTitle:@"Error" message: @"Camera permissions are restricted for scanning QR"];
        return;
    } else if (status == AVAuthorizationStatusAuthorized) {
        completion();
    } else if (status == AVAuthorizationStatusNotDetermined) {
        __weak __typeof(self) weakSelf = self;
        [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
            dispatch_async(dispatch_get_main_queue(), ^{
                if (granted) {
                    completion();
                } else {
                    [weakSelf showAlertWithTitle:@"Error" message: @"Camera permissions are required for scanning QR. Please turn on Settings -> MasterpassQR Demo -> Camera"];
                }
            });
        }];
    }
}

- (void)showAlertWithTitle:(NSString *)title message:(NSString *)message {
    UIAlertController *controller = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
    [controller addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleCancel handler:nil]];
    [self presentViewController:controller animated:true completion:nil];
}

# pragma mark - QRCodeReaderViewControllerDelegate Methods
- (void)reader:(QRCodeReaderViewController *)reader didScanResult:(NSString *)result {
    [reader stopScanning];
    [self dismissViewControllerAnimated:YES completion: nil];
}

- (void)readerDidCancel:(QRCodeReaderViewController *)reader {
    [reader stopScanning];
    [self dismissViewControllerAnimated:YES completion: nil];
}

```

### Advanced {#advanced}

#### Custom View Controller {#custom-view-controller}

Using custom view controller with `QRReaderViewController` embedded as child view controller.

Create new class `CustomViewController`:
* Swift
* Objective-C

```Swift
import MPQRScanSDK

class CustomViewController : UIViewController, QRCodeReaderDelegate {

  lazy var reader: QRCodeReaderViewController = {
    return QRCodeReaderViewController(builder: QRCodeReaderViewControllerBuilder {
        let readerView = $0.readerView

        // Setup overlay view
        let overlayView = readerView.getOverlay()
        overlayView.cornerColor = UIColor.purple
        overlayView.cornerWidth = 6
        overlayView.cornerLength = 75
        overlayView.indicatorSize = CGSize(width: 250, height: 250)

        // Setup scanning region
        $0.scanRegionSize = CGSize(width: 250, height: 250)

        // Hide torch button provided by the default view
        $0.showTorchButton = false

        // Hide cancel button provided by the default view
        $0.showCancelButton = false

        // Don't start scanning when this view is loaded i.e initialized
        $0.startScanningAtLoad = false

        $0.showSwitchCameraButton = false;

    })
  }()

  override func viewDidLoad() {
      super.viewDidLoad()

      reader.delegate = self

      self.addChildViewController(reader)
      self.view.insertSubview(reader.view, at: 0)

      let viewDict = ["reader" : reader.view as Any]
      self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[reader]|", options: [], metrics: nil, views: viewDict))
      self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[reader]|", options: [], metrics: nil, views: viewDict))

      reader.didMove(toParentViewController: self)
  }

  override func viewDidAppear(_ animated: Bool) {
      super.viewDidAppear(animated)
      reader.startScanning()
  }

  override func viewWillDisappear(_ animated: Bool) {
      super.viewWillDisappear(animated)
      reader.stopScanning()
  }

  // MARK:- Actions
  @IBAction func toggleTorch(_ sender: Any) {
      reader.codeReader!.toggleTorch()
  }

  func reader(_ reader: QRCodeReaderViewController, didScanResult result: String) {
      reader.stopScanning()
  }

  func readerDidCancel(_ reader: QRCodeReaderViewController) {
      reader.stopScanning()
  }
}

```

```Objective-C
@import MPQRScanSDK;

@interface CustomViewController () <QRCodeReaderDelegate>

@property (nonatomic, strong) QRCodeReaderViewController *qrVC;

@end

@implementation CustomViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    QRCodeReaderViewControllerBuilder *builder = [[QRCodeReaderViewControllerBuilder alloc] init];
    QRCodeReaderView *readerView = (QRCodeReaderView *) builder.readerView;

    // Setup overlay view
    QRCodeReaderViewOverlay *overlayView = (QRCodeReaderViewOverlay *)[readerView getOverlay];
    overlayView.cornerColor = UIColor.purpleColor;
    overlayView.cornerWidth = 6;
    overlayView.cornerLength = 75;
    overlayView.indicatorSize = CGSizeMake(250, 250);

    // Setup scanning region
    builder.scanRegionSize = CGSizeMake(250, 250);

    // Hide torch button provided by default view
    builder.showTorchButton = false;

    // Hide cancel button provided by default view
    builder.showCancelButton = false;

    // Don't start scanning when this view is loaded i.e initialized
    builder.startScanningAtLoad = false;

    builder.showSwitchCameraButton = false;

    self.qrVC = [[QRCodeReaderViewController alloc] initWithBuilder:builder];
    self.qrVC.delegate = self;

    // Add the reader as child view controller
    [self addChildViewController:self.qrVC];

    // Add reader view to the bottom
    [self.view insertSubview:self.qrVC.view atIndex: 0];

    NSDictionary *dictionary = @{@"qrVC": self.qrVC.view};
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[qrVC]|" options:0 metrics:nil views:dictionary]];
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[qrVC]|" options:0 metrics:nil views:dictionary]];

    [self.qrVC didMoveToParentViewController:self];
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    [self.qrVC startScanning];
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];
    [self.qrVC stopScanning];
}

#pragma mark - Actions
- (IBAction)torchButtonPressed:(id)sender {
    [self.qrVC.codeReader toggleTorch];
}

#pragma mark - QRCodeReaderViewControllerDelegate methods
- (void)reader:(QRCodeReaderViewController *)reader didScanResult:(NSString *)result {
    [reader stopScanning];
}

- (void)readerDidCancel:(QRCodeReaderViewController *)reader {
    [reader stopScanning];
}
```

#### Subclass of QRCodeReaderViewController {#subclass-of-qrcodereaderviewcontroller}

You can subclass the `QRCodeReaderViewController` to create your own look and feel and placement of the component of the class. You can modify the following components:

* cameraView
* cancelButton
* toggleTorchButton

* Swift
* Objective-C

```Swift
import UIKit
import MPQRScanSDK

class QRCodeReaderViewControllerSubClass: QRCodeReaderViewController {
    
    override func setupUIComponents(withCancelButtonTitle cancelButtonTitle: String?, cameraView: QRCodeReaderView?) {
        if let cameraView = cameraView {
            self.cameraView = cameraView;
        }else
        {
            self.cameraView = QRCodeReaderView()
        }
        
        //Customized Camera view
        let overlayView = self.cameraView!.getOverlay()
        overlayView.cornerColor = UIColor.green
        overlayView.cornerWidth = 6
        overlayView.cornerLength = 75
        overlayView.indicatorSize = CGSize(width: 250, height: 250)
        view.addSubview(self.cameraView!)

        //Customized Cancel Button
        if cancelButtonTitle != nil, self.showCancelButton {
            self.cancelButton = UIButton.init();
            if let cancelButton = self.cancelButton {
                cancelButton.translatesAutoresizingMaskIntoConstraints = false
                //Developer can customize image, title and other attributes for button
                cancelButton.setTitle(cancelButtonTitle, for: .normal)
                cancelButton.setTitleColor(UIColor.yellow, for: .normal)
                cancelButton.addTarget(self, action: #selector(clickedCancel), for: .touchUpInside)
                view.addSubview(cancelButton)
            }
        }
        
        //Customize Toggle Torch Button
        if showTorchButton == true, codeReader?.isTorchAvailable() == true {
            self.toggleTorchButton = UIButton.init()
            if let toggleTorchBtn = self.toggleTorchButton {
                toggleTorchBtn.translatesAutoresizingMaskIntoConstraints = false
                //Developer can customize image, title and other attributes for button
                toggleTorchBtn.setImage(UIImage.init(named: "torchBtn"), for: .normal)
                toggleTorchBtn.addTarget(self, action: #selector(clickedToggleTorch), for: .touchUpInside)
                view.addSubview(toggleTorchBtn)
            }
        }
    }
    
    //Handle Cancel button click event
    @objc private func clickedCancel() {
        self.dismiss(animated: true, completion: nil)
    }
    
    //Handle Toggle torch button click event
    @objc private func clickedToggleTorch() {
        codeReader?.toggleTorch()
    }
    
    override func setupAutoLayoutConstraints() {
        if self.cancelButton != nil {
            //view constraints if cancel button is present
            let views = ["cameraView":self.cameraView!, "cancelButton":self.cancelButton!]
            view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[cameraView][cancelButton(40)]|", options: NSLayoutConstraint.FormatOptions(rawValue: 0), metrics: nil, views: views))
            view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-50-[cameraView]-50-|", options: NSLayoutConstraint.FormatOptions(rawValue: 0), metrics: nil, views: views))
            view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-50-[cancelButton]-50-|", options: NSLayoutConstraint.FormatOptions(rawValue: 0), metrics: nil, views: views))
        }else{
            //view constraints if cancel button is absent
            let views = ["cameraView":self.cameraView!]
            view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-50-[cameraView]-50-|", options: NSLayoutConstraint.FormatOptions(rawValue: 0), metrics: nil, views: views))
            view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-50-[cameraView]-50-|", options: NSLayoutConstraint.FormatOptions(rawValue: 0), metrics: nil, views: views))

        }
        
        if self.toggleTorchButton != nil {
            //toggleTorchButton constraints for iOS 11+ and below
            if #available(iOS 11.0, *) {
                NSLayoutConstraint.activate([
                    self.toggleTorchButton!.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 30),
                    self.toggleTorchButton!.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor, constant: 10)
                ])
            } else {
                let topLayoutGuide = self.topLayoutGuide;
                let views = ["toggleTorchButton":self.toggleTorchButton!, "topLayoutGuide": topLayoutGuide] as [String : Any]
                view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[topLayoutGuide]-[toggleTorchButton(50)]", options: NSLayoutConstraint.FormatOptions(rawValue: 0), metrics: nil, views: views))
                view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[toggleTorchButton(70)]", options: NSLayoutConstraint.FormatOptions(rawValue: 0), metrics: nil, views: views))
            }
        }
    }
}
```

```Objective-C
#import <UIKit/UIKit.h>
#import <MPQRScanSDK/MPQRScanSDK.h>

@interface QRCodeReaderViewControllerSubClass : QRCodeReaderViewController

@end

@implementation QRCodeReaderViewControllerSubClass

#pragma mark - Overriden

- (void)setupUIComponentsWithCancelButtonTitle:(NSString *)cancelButtonTitle cameraView:(QRCodeReaderView *)cameraView{
    self.cameraView = cameraView;
    if (!self.cameraView) {
        self.cameraView = [[QRCodeReaderView alloc] init];
        self.cameraView.translatesAutoresizingMaskIntoConstraints = NO;
        self.cameraView.clipsToBounds = YES;
    }
    
    //Customized Camera view
    QRCodeReaderViewOverlay *overlayView = (QRCodeReaderViewOverlay *)[self.cameraView getOverlay];
    overlayView.cornerColor = UIColor.greenColor;
    overlayView.cornerWidth = 6;
    overlayView.cornerLength = 75;
    overlayView.indicatorSize = CGSizeMake(250, 250);
    [self.view addSubview:self.cameraView];

    //Customized Cancel Button
    if (self.showCancelButton) {
        self.cancelButton = [[UIButton alloc] init];
        self.cancelButton.translatesAutoresizingMaskIntoConstraints = NO;
        //Developer can customize image, title and other attributes for button
        [self.cancelButton setTitle:cancelButtonTitle forState:UIControlStateNormal];
        [self.cancelButton setTitleColor:[UIColor yellowColor] forState:UIControlStateNormal];
        [self.cancelButton addTarget:self action:@selector(clickedCancel) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview: self.cancelButton];
    }
    
    //Customize Toggle Torch Button
    if (self.showTorchButton) {
        self.toggleTorchButton = [[UIButton alloc] init];
        self.toggleTorchButton.translatesAutoresizingMaskIntoConstraints = NO;
        //Developer can customize image, title and other attributes for button
        [self.toggleTorchButton setImage:[UIImage imageNamed:@"torchBtn"] forState:UIControlStateNormal];
        [self.toggleTorchButton addTarget:self action:@selector(clickedToggleTorch) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview: self.toggleTorchButton];
    }
}

//Handle Cancel button click event
- (void)clickedCancel{
    [self dismissViewControllerAnimated:YES completion:nil];
}

//Handle Toggle torch button click event
- (void)clickedToggleTorch{
    [self.codeReader toggleTorch];
}

- (void)setupAutoLayoutConstraints
{
    if (self.cancelButton) {
        //view constraints if cancel button is present
        NSDictionary *views = [NSDictionary dictionaryWithObjectsAndKeys:self.cameraView, @"cameraView", self.cancelButton, @"cancelButton", nil];
        [self.view addConstraints:
         [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[cameraView][cancelButton(40)]|" options:0 metrics:nil views:views]];
        [self.view addConstraints:
         [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[cameraView]|" options:0 metrics:nil views:views]];
        [self.view addConstraints:
         [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[cancelButton]-|" options:0 metrics:nil views:views]];
    }else {
        //view constraints if cancel button is absent
        NSDictionary *views = [NSDictionary dictionaryWithObjectsAndKeys:@"cameraView", self.cameraView, nil];
        [self.view addConstraints:
         [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[cameraView]|" options:0 metrics:nil views:views]];
        [self.view addConstraints:
         [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[cameraView]|" options:0 metrics:nil views:views]];
    }
    
    if (self.toggleTorchButton != nil) {
        //toggleTorchButton constraints for iOS 11+ and below
        if (@available(iOS 11.0, *)) {
            [[[self.toggleTorchButton topAnchor] constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor constant:30] setActive: YES];
            [[[self.toggleTorchButton leadingAnchor] constraintEqualToAnchor:self.view.safeAreaLayoutGuide.leadingAnchor constant:10] setActive: YES];
        } else {
            id topLayoutGuide = self.topLayoutGuide;
            NSDictionary *torchViews = [NSDictionary dictionaryWithObjectsAndKeys:self.toggleTorchButton, @"toggleTorchButton", topLayoutGuide, @"topLayoutGuide", nil];
            [self.view addConstraints:
             [NSLayoutConstraint constraintsWithVisualFormat:@"V:[topLayoutGuide]-[toggleTorchButton(50)]" options:0 metrics:nil views:torchViews]];
            [self.view addConstraints:
             [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[toggleTorchButton(70)]" options:0 metrics:nil views:torchViews]];
        }
        
    }
}

@end
```

