Rewrote iOS scanner in ObjC to avoid swift plugin conflicts with use_frameworks!

This commit is contained in:
Matthew Smith 2017-11-07 18:27:25 -08:00
parent 3cea973135
commit 80b5c6f3eb
8 changed files with 44 additions and 267 deletions

View File

@ -6,7 +6,6 @@ if ENV['FLUTTER_FRAMEWORK_DIR'] == nil
end end
target 'Runner' do target 'Runner' do
use_frameworks!
# Pods for Runner # Pods for Runner

View File

@ -20,6 +20,6 @@ SPEC CHECKSUMS:
Flutter: d674e78c937094a75ac71dd77e921e840bea3dbf Flutter: d674e78c937094a75ac71dd77e921e840bea3dbf
MTBBarcodeScanner: 66c75f40cbac5986c97634a0775bfe7f92243e7c MTBBarcodeScanner: 66c75f40cbac5986c97634a0775bfe7f92243e7c
PODFILE CHECKSUM: cc70c01bca487bebd110b87397f017f3b76a89f1 PODFILE CHECKSUM: 407db753d18e8726329521f96a8fa0bebaef3f42
COCOAPODS: 1.3.1 COCOAPODS: 1.3.1

View File

@ -11,6 +11,7 @@
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
5CB758D7343A0FB9D27CC91C /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C8072F22B7B32BA6730C5387 /* libPods-Runner.a */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
@ -20,7 +21,6 @@
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
A969C0372EF8436F14E8E3DB /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 178DF4CC418BA343104EED78 /* Pods_Runner.framework */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */ /* Begin PBXCopyFilesBuildPhase section */
@ -41,7 +41,6 @@
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
178DF4CC418BA343104EED78 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; }; 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
@ -56,6 +55,7 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
C8072F22B7B32BA6730C5387 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -65,7 +65,7 @@
files = ( files = (
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
A969C0372EF8436F14E8E3DB /* Pods_Runner.framework in Frameworks */, 5CB758D7343A0FB9D27CC91C /* libPods-Runner.a in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -75,7 +75,7 @@
3C7717B73810891D4425A279 /* Frameworks */ = { 3C7717B73810891D4425A279 /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
178DF4CC418BA343104EED78 /* Pods_Runner.framework */, C8072F22B7B32BA6730C5387 /* libPods-Runner.a */,
); );
name = Frameworks; name = Frameworks;
sourceTree = "<group>"; sourceTree = "<group>";
@ -227,9 +227,12 @@
files = ( files = (
); );
inputPaths = ( inputPaths = (
"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-resources.sh",
"${PODS_ROOT}/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Versions/A/Resources/GoogleMaps.bundle",
); );
name = "[CP] Copy Pods Resources"; name = "[CP] Copy Pods Resources";
outputPaths = ( outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}",
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
@ -290,14 +293,10 @@
inputPaths = ( inputPaths = (
"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", "${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
"${PODS_ROOT}/../../../../../../flutter/bin/cache/artifacts/engine/ios/Flutter.framework", "${PODS_ROOT}/../../../../../../flutter/bin/cache/artifacts/engine/ios/Flutter.framework",
"${BUILT_PRODUCTS_DIR}/MTBBarcodeScanner/MTBBarcodeScanner.framework",
"${BUILT_PRODUCTS_DIR}/barcode_scan/barcode_scan.framework",
); );
name = "[CP] Embed Pods Frameworks"; name = "[CP] Embed Pods Frameworks";
outputPaths = ( outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MTBBarcodeScanner.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/barcode_scan.framework",
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;

View File

@ -1,6 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:barcode_scan/barcode_scanner.dart'; import 'package:barcode_scan/barcode_scan.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
void main() { void main() {

View File

@ -1,4 +1,9 @@
#import <Flutter/Flutter.h> #import <Flutter/Flutter.h>
#import "BarcodeScannerViewControllerDelegate.h"
@interface BarcodeScanPlugin : NSObject<FlutterPlugin>
@interface BarcodeScanPlugin : NSObject<FlutterPlugin, BarcodeScannerViewControllerDelegate>
@property(nonatomic, retain) FlutterResult result;
@property (nonatomic, assign) UIViewController *hostViewController;
@end @end

View File

@ -1,8 +1,35 @@
#import "BarcodeScanPlugin.h" #import "BarcodeScanPlugin.h"
#import <barcode_scan/barcode_scan-Swift.h> #import "BarcodeScannerViewController.h"
@implementation BarcodeScanPlugin @implementation BarcodeScanPlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar { + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
[SwiftBarcodeScanPlugin registerWithRegistrar:registrar]; FlutterMethodChannel *channel = [FlutterMethodChannel methodChannelWithName:@"com.apptreesoftware.barcode_scan"
binaryMessenger:registrar.messenger];
BarcodeScanPlugin *instance = [BarcodeScanPlugin new];
instance.hostViewController = [UIApplication sharedApplication].delegate.window.rootViewController;
[registrar addMethodCallDelegate:instance channel:channel];
} }
- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result {
if ([@"scan" isEqualToString:call.method]) {
self.result = result;
[self showBarcodeView];
} else {
result(FlutterMethodNotImplemented);
}
}
- (void)showBarcodeView {
BarcodeScannerViewController *scannerViewController = [[BarcodeScannerViewController alloc] init];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:scannerViewController];
scannerViewController.delegate = self;
[self.hostViewController presentViewController:navigationController animated:NO completion:nil];
}
- (void)barcodeScannerViewController:(BarcodeScannerViewController *)controller didScanBarcodeWithResult:(NSString *)result {
if (self.result) {
self.result(result);
}
}
@end @end

View File

@ -1,213 +0,0 @@
//
// BarcodeScannerViewController.swift
// AppTreeRevolution
//
// Created by Matthew Smith on 2/18/15.
// Copyright (c) 2015 AppTree Software. All rights reserved.
//
import UIKit
import MTBBarcodeScanner
protocol BarcodeScannerViewControllerDelegate : class {
func barcodeScannerViewController(_ controller : BarcodeScannerViewController, didScanBarcodeWithResult result: String)
}
struct BarcodeScanContext {
var attributeIndex : Int?
}
protocol TorchSupport {
var flashOn: Bool { get }
var hasTorch: Bool { get }
func toggleFlash(to flashOn: Bool)
}
extension TorchSupport {
var flashOn: Bool {
if let device = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo) {
return device.torchMode == AVCaptureTorchMode.on && device.flashMode == AVCaptureFlashMode.on
}
return false
}
var hasTorch: Bool {
let device = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
return device?.hasTorch ?? false
}
func toggleFlash(to flashOn: Bool) {
let device : AVCaptureDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
do {
if device.hasFlash && device.hasTorch {
try device.lockForConfiguration()
if flashOn {
device.flashMode = AVCaptureFlashMode.on
device.torchMode = AVCaptureTorchMode.on
} else {
device.flashMode = AVCaptureFlashMode.off
device.torchMode = AVCaptureTorchMode.off
}
device.unlockForConfiguration()
}
} catch {
print("Could not lock the camera configuration")
}
}
}
class BarcodeScannerOverlayView : UIView, TorchSupport {
var turnLightOnLabel : UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
_init()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
_init()
}
override func awakeFromNib() {
super.awakeFromNib()
}
func _init() {
turnLightOnLabel = UILabel()
turnLightOnLabel.translatesAutoresizingMaskIntoConstraints = false
turnLightOnLabel.textColor = UIColor.white
turnLightOnLabel.layer.cornerRadius = 8.0
turnLightOnLabel.clipsToBounds = true
turnLightOnLabel.alpha = 0.8
turnLightOnLabel.text = "Tap to toggle flash"
self.addSubview(turnLightOnLabel)
self.addConstraints(NSLayoutConstraint
.constraints(withVisualFormat: "V:|[turnLightOnLabel(20)]-20-|",
options: NSLayoutFormatOptions.alignAllBottom,
metrics: nil,
views: ["turnLightOnLabel" : turnLightOnLabel]))
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
UIView.animate(withDuration: 0.35, animations: {
self.turnLightOnLabel.alpha = 0.0
})
self.toggleFlash(to: true)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
UIView.animate(withDuration: 0.35, animations: {
self.turnLightOnLabel.alpha = 0.8
})
self.toggleFlash(to: false)
}
}
class BarcodeScannerViewController: UIViewController, TorchSupport {
var overlayView: BarcodeScannerOverlayView!
var previewView: UIView!
var scanner : MTBBarcodeScanner!
var context : BarcodeScanContext = BarcodeScanContext()
weak var delegate : BarcodeScannerViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
self.previewView = UIView(frame: self.view.bounds)
self.previewView.translatesAutoresizingMaskIntoConstraints = false
self.overlayView = BarcodeScannerOverlayView(frame: self.view.bounds)
self.overlayView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(previewView)
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[previewView]", options: NSLayoutFormatOptions.alignAllBottom, metrics: nil, views: ["previewView" : previewView]))
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:[previewView]", options: NSLayoutFormatOptions.alignAllBottom, metrics: nil, views: ["previewView" : previewView]))
// self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[overlayView]", options: NSLayoutFormatOptions.alignAllBottom, metrics: nil, views: ["overlayView" : overlayView]))
// self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:[overlayView]", options: NSLayoutFormatOptions.alignAllBottom, metrics: nil, views: ["overlayView" : overlayView]))
scanner = MTBBarcodeScanner(previewView: previewView)
self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(BarcodeScannerViewController.cancel))
updateFlashButton()
}
private func updateFlashButton() {
if !self.hasTorch {
return
}
if self.flashOn {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Flash Off", style: .plain, target: self, action: #selector(BarcodeScannerViewController.toggle))
} else {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Flash On", style: .plain, target: self, action: #selector(BarcodeScannerViewController.toggle))
}
}
func toggle() {
self.toggleFlash(to: !flashOn)
self.updateFlashButton()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if self.scanner.isScanning() {
self.scanner.stopScanning()
}
MTBBarcodeScanner.requestCameraPermission { (success : Bool) -> Void in
if success {
self.startScan()
} else {
self.showNoCameraAccessAlert()
}
}
}
func cancel() {
self.dismiss(animated: true, completion: nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.scanner.stopScanning()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
if self.flashOn {
self.toggleFlash(to: false)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
fileprivate func startScan() {
do {
try self.scanner.startScanning(resultBlock: {[weak self] results in
if let code = results?.first {
self?.scanner.stopScanning()
if let strongSelf = self {
self?.delegate?.barcodeScannerViewController(strongSelf, didScanBarcodeWithResult: code.stringValue)
self?.dismiss(animated: true, completion: nil)
}
}
})
} catch {
}
}
fileprivate func showNoCameraAccessAlert() {
let alertController = UIAlertController(title: "Camera access denied", message: "Camera access has been disabled for this application. To turn camera access back on, please go to the iOS settings application.", preferredStyle: .alert);
alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in
_ = self.navigationController?.popViewController(animated: true)
}))
self.present(alertController, animated: true, completion: nil)
}
}

View File

@ -1,40 +0,0 @@
import Flutter
import UIKit
import MTBBarcodeScanner
public class SwiftBarcodeScanPlugin: NSObject, FlutterPlugin, BarcodeScannerViewControllerDelegate {
private var result : FlutterResult?
private var hostViewController : UIViewController?
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "com.apptreesoftware.barcode_scan", binaryMessenger: registrar.messenger());
let instance = SwiftBarcodeScanPlugin();
instance.hostViewController = UIApplication.shared.delegate?.window!?.rootViewController
registrar.addMethodCallDelegate(instance, channel: channel);
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "scan":
self.result = result;
showBarcodeView();
break;
default:
result(FlutterMethodNotImplemented);
}
}
private func showBarcodeView() {
let viewController = BarcodeScannerViewController()
let navController = UINavigationController(rootViewController: viewController)
viewController.delegate = self
self.hostViewController?.present(navController, animated: true, completion: nil);
}
func barcodeScannerViewController(_ controller : BarcodeScannerViewController, didScanBarcodeWithResult result: String) {
if let callback = self.result {
callback(result)
}
}
}