Swift Delegates and SocketScan scanApi.setDelegate()

338 Views Asked by At

The SocketScan API for iOS is written in Objective-C and is intended to integrate SocketMobile scanners in CHS mode into native applications.

When the ScanApi is initiated, requires the ViewController to be set as a ScanApiHelperDelegate, which requires some setup on the viewDidLoad as well as a few functions for receiving actions from the scanner.

Vars are setup:

var scanApi = ScanApiHelper()
var scanApiConsumer = NSTimer()

Then in the viewDidLoad, the following code is implemented:

scanApi.setDelegate(self)
scanApi.open()
scanApiConsumer = NSTimer.scheduledTimerWithTimeInterval(0.2, target: self, selector:Selector("onTimer"), userInfo: nil, repeats: true)

Then, the following function is called by scanApiConsumer to listen for notifications from the scanner:

func onTimer () -> Void{
    scanApi.doScanApiReceive()
    scanApi.getDevicesList()
}

When a barcode is scanned, it performs the following:

func onDecodedData(device: DeviceInfo, decodedData: ISktScanDecodedData) {
    //code to execute here
}

If needed, the user is routed to another view where scanning a barcode executes different code. The scanApi.setDelegate is set like this:

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if segue.identifier == "manifestToRepackContainer" {
        let vc = (segue.destinationViewController as RepackContainer)
        scanApi.setDelegate(vc)
        vc.containerBarcode = GlobalVars.barcodeData
    }
}

That works great too. Once you are there, the scanner performs the designated functions properly. And, on viewDidAppear on the original viewer, I have this setup, which also works as intended:

override func viewDidAppear(animated: Bool) {
    scanApi.setDelegate(self)
}

The issue arises when the user needs scanning functionality in a completely different area of the storyboard (the 'remote' view), where a prepareForSegue method isn't possible to trigger a scanApi.setDelegate()

My initial thought was to simply define scanApi delegate in the "remote" view by executing a scanApi.setDelegate(self) in viewDidAppear on the "remote" view. Of course, this view is also designated as a scanApiHelperDelegate and contains all the needed functions. However, it doesn't work. The original viewController is still the delegate and scanning barcode continue to trigger the functions listed in the original viewController, not the current one. The compiler returns no errors.

I suspect it is either a) something about the way Swift interacts with the Objective-C API or b) i am incorrectly declaring what should be the new scanApi delegate by using "self".

So, what is the proper way to declare the new scanApi delegate in this scenario or is this an issue with the API?

1

There are 1 best solutions below

0
On

The current version of ScanApiHelper doesn't support multiple delegate views, but future versions will. Here are the modifications we made, if you'd like to add it to the ScanApiHelper in your project.

Note: The changes are backwards compatible, so you can safely apply these changes or upgrade to the latest version of ScanApiHelper when it is released even if you don't need to support multiple delegate views

ScanApiHelper.h

Index: ScanApiHelper.h
===================================================================
--- ScanApiHelper.h (revision 12778)
+++ ScanApiHelper.h (revision 12779)
@@ -65,6 +65,7 @@
 @end

 @protocol ScanApiHelperDelegate <NSObject>
+@optional
 /**
  * called each time a device connects to the host
  * @param result contains the result of the connection
@@ -103,7 +104,6 @@
  */
 -(void) onErrorRetrievingScanObject:(SKTRESULT) result;

-@optional
 /**
  * called each time ScanAPI receives decoded data from scanner
  * @param result is ESKT_NOERROR when decodedData contains actual
@@ -179,9 +179,15 @@
     id<ISktScanApi>_scanApi;
     id<ISktScanObject>_scanObjectReceived;
     NSObject* _commandContextsLock;
+    BOOL _shared;// to indicate when ScanApiHelper is shared across multiple views
+    NSMutableArray* _delegateStack;
 }

++(ScanApiHelper*)sharedScanApiHelper;

+-(void)pushDelegate:(id<ScanApiHelperDelegate>)delegate;
+-(void)popDelegate:(id<ScanApiHelperDelegate>)delegate;
+
 /**
  * register for notifications in order to receive notifications such as
  * "Device Arrival", "Device Removal", "Decoded Data"...etc...

ScanApiHelper.mm

Index: ScanApiHelper.mm
===================================================================
--- ScanApiHelper.mm    (revision 12778)
+++ ScanApiHelper.mm    (revision 12779)
@@ -105,12 +105,16 @@
 @implementation ScanApiHelper

 -(id)init{
+    static ScanApiHelper* sharedScanApiHelper=nil;
     self=[super init];
     if(self!=nil){
+        sharedScanApiHelper=self;
         _commandContextsLock=[[NSObject alloc]init];
         _deviceInfoList=[[NSMutableDictionary alloc]init];
         _scanApiOpen=FALSE;
         _scanApiTerminated=TRUE;// by default ScanApi is not started
+        _shared=NO;
+        _delegateStack=[[NSMutableArray alloc]init];
     }
     return self;
 }
@@ -130,6 +134,7 @@
     _commandContextsLock=nil;

     _deviceInfoList=nil;
+    _delegateStack=nil;
 }
 #else
 -(void)dealloc{
@@ -151,10 +156,54 @@

     [_deviceInfoList release];
     _deviceInfoList=nil;
+    
+    [_delegateStack release];
+    _delegateStack=nil;
+    
     [super dealloc];
 }
 #endif

++(ScanApiHelper*)sharedScanApiHelper{
+    static ScanApiHelper* scanApiHelper=nil;
+    if(scanApiHelper==nil){
+        scanApiHelper=[[ScanApiHelper alloc]init];
+        scanApiHelper->_shared=YES;
+    }
+    return scanApiHelper;
+}
+
+-(void)pushDelegate:(id<ScanApiHelperDelegate>)delegate{
+    if(_delegate != delegate){
+        if(_delegate!=nil){
+            [_delegateStack addObject:_delegate];
+        }
+        _delegate=delegate;
+        [self generateDeviceArrivals];
+    }
+}
+
+-(void)popDelegate:(id<ScanApiHelperDelegate>)delegate{
+    if(_delegate ==delegate){
+        if(_delegateStack.count>0){
+            id<ScanApiHelperDelegate> newDelegate=[_delegateStack objectAtIndex:_delegateStack.count-1];
+            [_delegateStack removeLastObject];
+            _delegate = newDelegate;
+            // generate a device Arrival for each scanner we've already receive
+            // so that the new view can be aware of the connected scanners
+            if(_delegate!=nil){
+                for (NSString* key in _deviceInfoList) {
+                    DeviceInfo* device=[_deviceInfoList objectForKey:key];
+                    [_delegate onDeviceArrival:ESKT_NOERROR device:device];
+                }
+            }
+        }
+        else{
+            _delegate=nil;
+        }
+    }
+}
+
 /**
  * register for notifications in order to receive notifications such as
  * "Device Arrival", "Device Removal", "Decoded Data"...etc...
@@ -1519,14 +1568,15 @@
 #endif    
     // release the previous ScanAPI object instance if
     // it exists
-    if(_scanApi!=nil){
-        [_scanApi close];
-        [SktClassFactory releaseScanApiInstance:_scanApi];
-    }
     _scanApi=[SktClassFactory createScanApiInstance];
     SKTRESULT result=[_scanApi open:nil];
-    if(_delegate!=nil)
-        [_delegate onScanApiInitializeComplete:result];
+    if((_delegate!=nil)&&([_delegate respondsToSelector:@selector(onScanApiInitializeComplete:)])){
+        [_delegate onScanApiInitializeComplete:result];
+    }
     _scanApiTerminated=FALSE;

 #if __has_feature(objc_arc)
@@ -1576,8 +1626,9 @@
             }
         }
         else{
-            if(_delegate!=nil)
+            if((_delegate!=nil)&&([_delegate respondsToSelector:@selector(onErrorRetrievingScanObject:)])){
                 [_delegate onErrorRetrievingScanObject:result];
+            }
         }
     }
     return result;
@@ -1600,8 +1651,9 @@
             result=[self handleDeviceRemoval:scanObj];
             break;
         case kSktScanMsgIdTerminate:
-            if(_delegate!=nil)
+            if((_delegate!=nil)&&([_delegate respondsToSelector:@selector(onScanApiTerminated)])){
                 [_delegate onScanApiTerminated];
+            }
             closeScanApi=TRUE;
             break;
         case kSktScanMsgSetComplete:
@@ -1619,8 +1671,9 @@

     // if there is an error then report it to the ScanAPIHelper user
     if(!SKTSUCCESS(result)){
-        if(_delegate!=nil)
+        if((_delegate!=nil)&&([_delegate respondsToSelector:@selector(onError:)])){
             [_delegate onError:result];
+        }
     }
     return closeScanApi;
 }
@@ -1657,8 +1710,9 @@
     }

     // notify the ScanApiHelper user a scanner has connected to this host
-    if(_delegate!=nil)
+    if((_delegate!=nil)&&([_delegate respondsToSelector:@selector(onDeviceArrival:device:)])){
         [_delegate onDeviceArrival:result device:deviceInfo];
+    }

 #if __has_feature(objc_arc)
 #else 
@@ -1702,8 +1756,9 @@
     [SktClassFactory releaseDeviceInstance:scanDevice];

     // notify the ScanApiHelper user a scanner has connected to this host
-    if(_delegate!=nil)
+    if((_delegate!=nil)&&([_delegate respondsToSelector:@selector(onDeviceRemoval:)])){
         [_delegate onDeviceRemoval:deviceInfo];
+    }

     return result;
 }
@@ -1766,7 +1821,7 @@
             result=[self handleDecodedData:scanObj];
             break;
         case kSktScanEventError:
-            if(_delegate!=nil)
+            if((_delegate!=nil)&&([_delegate respondsToSelector:@selector(onError:)]))
                 [_delegate onError:[[scanObj Msg]Result]];
             break;

@@ -1809,6 +1864,7 @@
     return result;
 }

 /**
  * sendNextCommand
  *
@@ -1855,4 +1911,25 @@
     return result;
 }

+/**
+ *  generateDeviceArrivals
+ *
+ *  internal function to generate device
+ *  arrival notifications when the delegate
+ *  change to a new delegate.
+ *  This is used in a multi views application
+ *  that has more than one view to receive the
+ *  decoded data
+ */
+-(void)generateDeviceArrivals{
+    // generate a device Arrival for each scanner we've already receive
+    // so that the new view can be aware of the connected scanners
+    if(_delegate!=nil){
+        for (NSString* key in _deviceInfoList) {
+            DeviceInfo* device=[_deviceInfoList objectForKey:key];
+            [_delegate onDeviceArrival:ESKT_NOERROR device:device];
+        }
+    }
+}
+
 @end