diff --git a/Classes/CardIOCardScanner.mm b/Classes/CardIOCardScanner.mm index 09417724..83c98422 100644 --- a/Classes/CardIOCardScanner.mm +++ b/Classes/CardIOCardScanner.mm @@ -24,8 +24,6 @@ @interface CardIOCardScanner () @property(assign, readwrite) ScannerState scannerState; @property(strong, readwrite) CardIOReadCardInfo *cardInfoCache; @property(assign, readwrite) BOOL cardInfoCacheDirty; -@property(strong, readwrite) NSArray *xOffsets; -@property(assign, readwrite) uint16_t yOffset; @property(assign, readwrite) BOOL lastFrameWasUsable; @property(assign, readwrite) BOOL lastFrameWasUpsideDown; @property(assign, readwrite) BOOL scanIsComplete; @@ -67,7 +65,6 @@ - (void)addFrame:(CardIOIplImage *)y } FrameScanResult result; - bool collectCardNumber = (_scannerState.timeOfCardNumberCompletionInMilliseconds == 0); // A little bit of a hack, but we prepopulate focusScore and brightness information result.focus_score = focusScore; @@ -79,20 +76,8 @@ - (void)addFrame:(CardIOIplImage *)y result.flipped = flipped; scanner_add_frame_with_expiry(&_scannerState, y.image, scanExpiry, &result); self.lastFrameWasUsable = result.usable; - if (collectCardNumber) { - if(result.usable) { - NSMutableArray *x = [NSMutableArray arrayWithCapacity:result.hseg.n_offsets]; - for(uint8_t i = 0; i < result.hseg.n_offsets; i++) { - NSNumber *xOffset = [NSNumber numberWithUnsignedShort:result.hseg.offsets[i]]; - [x addObject:xOffset]; - } - self.xOffsets = x; - self.yOffset = result.vseg.y_offset; - } else { - self.lastFrameWasUpsideDown = result.upside_down; - self.xOffsets = nil; - self.yOffset = 0; - } + if(!result.usable) { + self.lastFrameWasUpsideDown = result.upside_down; } [self markCachesDirty]; } @@ -156,9 +141,15 @@ - (CardIOReadCardInfo *)cardInfo { } #endif + NSMutableArray *xOffsets = [NSMutableArray arrayWithCapacity:result.hseg.n_offsets]; + for(uint8_t i = 0; i < result.hseg.n_offsets; i++) { + NSNumber *xOffset = [NSNumber numberWithUnsignedShort:result.hseg.offsets[i]]; + [xOffsets addObject:xOffset]; + } + self.cardInfoCache = [CardIOReadCardInfo cardInfoWithNumber:cardNumber - xOffsets:self.xOffsets - yOffset:self.yOffset + xOffsets:xOffsets + yOffset:result.vseg.y_offset expiryMonth:result.expiry_month expiryYear:result.expiry_year #if CARDIO_DEBUG diff --git a/Classes/CardIOCreditCardExpiryFormatter.m b/Classes/CardIOCreditCardExpiryFormatter.m index 1efc205a..a5798399 100644 --- a/Classes/CardIOCreditCardExpiryFormatter.m +++ b/Classes/CardIOCreditCardExpiryFormatter.m @@ -5,6 +5,7 @@ #import "CardIOCreditCardExpiryFormatter.h" #import "CardIOCreditCardInfo.h" +#import "CardIOCreditCardNumber.h" @implementation CardIOCreditCardExpiryFormatter @@ -16,12 +17,12 @@ - (BOOL)getObjectValue:(id __autoreleasing *)obj forString:(NSString *)string er NSInteger year = 0; if (values.count > 0) { - month = [[values[0] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] integerValue]; + month = [[CardIOCreditCardNumber stringByRemovingNonNumbers:values[0]] integerValue]; } if (values.count > 1) { - year = [[values[1] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] integerValue]; + year = [[CardIOCreditCardNumber stringByRemovingNonNumbers:values[1]] integerValue]; if (year < 2000) { - year += 2000; + year = 2000 + (year % 100); } } diff --git a/Classes/CardIOExpiryTextFieldDelegate.m b/Classes/CardIOExpiryTextFieldDelegate.m index 9bfd2639..fda91e9f 100644 --- a/Classes/CardIOExpiryTextFieldDelegate.m +++ b/Classes/CardIOExpiryTextFieldDelegate.m @@ -94,7 +94,8 @@ - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRang NSString *numericNewText = [CardIOCreditCardNumber stringByRemovingNonNumbers:newText]; NSString *updatedText = [textField.text stringByReplacingCharactersInRange:range withString:numericNewText]; - if(updatedText.length > 9) { + if(updatedText.length > 7) { + // 7 characters: "MM_/_YY" [CardIOConfigurableTextFieldDelegate vibrate]; return NO; } @@ -104,7 +105,7 @@ - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRang NSString *monthStr = [updatedNumberText substringToIndex:MIN(2, updatedNumberText.length)]; if(monthStr.length > 0) { NSInteger month = [monthStr integerValue]; - if(month < 0 || 12 < month) { + if(month < 0 || month > 12) { [CardIOConfigurableTextFieldDelegate vibrate]; return NO; } diff --git a/Classes/CardIOIplImage.mm b/Classes/CardIOIplImage.mm index abe991e3..d51073ea 100644 --- a/Classes/CardIOIplImage.mm +++ b/Classes/CardIOIplImage.mm @@ -110,12 +110,13 @@ - (UIImage *)UIImage { } else if(self.image->nChannels == 3) { colorSpace = CGColorSpaceCreateDeviceRGB(); } + int depth = self.image->depth & ~IPL_DEPTH_SIGN; NSData *data = [NSData dataWithBytes:self.image->imageData length:self.image->imageSize]; CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data); CGImageRef imageRef = CGImageCreate(self.image->width, self.image->height, - self.image->depth, - self.image->depth * self.image->nChannels, + depth, + depth * self.image->nChannels, self.image->widthStep, colorSpace, kCGImageAlphaNone | kCGBitmapByteOrderDefault, diff --git a/Classes/CardIOUtilities.m b/Classes/CardIOUtilities.m index 3c60bd09..8493d9f7 100644 --- a/Classes/CardIOUtilities.m +++ b/Classes/CardIOUtilities.m @@ -8,8 +8,11 @@ #import "CardIOGPUGaussianBlurFilter.h" #import "CardIOIccVersion.h" #import "CardIOLocalizer.h" +#import "CardIOMacros.h" #import "CardIOView.h" +#import + @implementation CardIOUtilities #pragma mark - Library version, for bug reporting etc. @@ -70,6 +73,30 @@ + (BOOL)canReadCardWithCamera { cachedScanAvailabilityStatus = ScanAvailabilityNever; return NO; } + + if (iOS_7_PLUS) { + // Check for video permission. + // But don't set cachedScanAvailabilityStatus here, as the user can change this permission at any time. + // (Actually, should the user go to Settings and change this permission for this app, apparently the system + // will immediately SIGKILL (force restart) this app. But let's not depend on this semi-documented behavior.) + AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]; + if (authStatus == AVAuthorizationStatusDenied || authStatus == AVAuthorizationStatusRestricted){ + return NO; + } + else { + // Either the user has already granted permission, or else the user has not yet been asked. + // + // For the latter case, while we could ask now, unfortunately the necessary + // [AVCaptureDevice requestAccessForMediaType:completionHandler:] method returns the user's choice + // to us asynchronously, which doesn't mix well with canReadCardWithCamera being synchronous. + // + // Rather than making a backward-incompatible change to canReadCardWithCamera, let's simply allow things + // to proceed. When the camera view is finally presented, then the user will be prompted to authorize + // or deny the video permission. If they choose "deny", then they'll probably understand why they're + // looking at a black screen. + return YES; + } + } #endif cachedScanAvailabilityStatus = ScanAvailabilityAlways; diff --git a/Classes/CardIOVideoStream.mm b/Classes/CardIOVideoStream.mm index 267d843e..bfd1ad3f 100644 --- a/Classes/CardIOVideoStream.mm +++ b/Classes/CardIOVideoStream.mm @@ -496,6 +496,21 @@ - (void)startSession { - (void)stopSession { if (self.running) { +#if USE_CAMERA + [self changeCameraConfiguration:^{ + // restore default focus range + if ([self.camera respondsToSelector:@selector(isAutoFocusRangeRestrictionSupported)]) { + if(self.camera.autoFocusRangeRestrictionSupported) { + self.camera.autoFocusRangeRestriction = AVCaptureAutoFocusRangeRestrictionNone; + } + } + } + #if CARDIO_DEBUG + withErrorMessage:@"CardIO couldn't lock for configuration within stopSession" + #endif + ]; +#endif + dispatch_semaphore_wait(self.cameraConfigurationSemaphore, DISPATCH_TIME_FOREVER); #if USE_CAMERA diff --git a/Classes/CardIOViewController.mm b/Classes/CardIOViewController.mm index c63ce22b..6561c59e 100644 --- a/Classes/CardIOViewController.mm +++ b/Classes/CardIOViewController.mm @@ -457,6 +457,10 @@ - (void)manualEntry:(id)sender { - (void)cancel:(id)sender { [self.context.scanReport reportEventWithLabel:@"scan_cancel" withScanner:self.cardIOView.scanner]; + + // Hiding the CardIOView causes it to call its stopSession method, thus eliminating a visible stutter. + // See https://github.com/card-io/card.io-iOS-SDK/issues/97 + self.cardIOView.hidden = YES; [self.navigationController setNavigationBarHidden:NO animated:YES]; // to restore the color of the status bar! diff --git a/Release/CardIO.podspec b/Release/CardIO.podspec index 3b0f0e66..2a3d3c79 100644 --- a/Release/CardIO.podspec +++ b/Release/CardIO.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'CardIO' - spec.version = '5.0.1' + spec.version = '5.0.4' spec.license = { type: 'MIT', file: 'LICENSE.md' } spec.homepage = 'https://www.card.io' spec.authors = { 'CardIO' => 'support@paypal.com' } diff --git a/Release/SampleApp-Swift/SampleApp-Swift/Base.lproj/Main.storyboard b/Release/SampleApp-Swift/SampleApp-Swift/Base.lproj/Main.storyboard index 5966c55d..345e16db 100644 --- a/Release/SampleApp-Swift/SampleApp-Swift/Base.lproj/Main.storyboard +++ b/Release/SampleApp-Swift/SampleApp-Swift/Base.lproj/Main.storyboard @@ -1,7 +1,7 @@ - + - + @@ -23,11 +23,11 @@ - + -