二维码扫码效果(多个二维码识别和点选)

类似微信扫码效果,会标记识别到的二维码,并且识别到多个二维码时,可以允许用户点选。

二维码坐标解析步骤:

1摄像头扫描

首先说明:摄像头获取的视频流和屏幕尺寸比例不一定一样,获取的二维码坐标及角点是以视频流的坐标系为基准的比例值(范围0-1)。
本次代码以videoPreviewLayer(用于展示摄像头捕获的视频流)frame设为全屏,setVideoGravity参数设为AVLayerVideoGravityResizeAspectFill为例进行说明。


左边的计算差值,右边的计算需要补值,原理一样

扫描代理方法返回的数据为AVMetadataMachineReadableCodeObject数组(若是设置为人脸扫描等其它模式,则返回对应类型的数组)
AVMetadataMachineReadableCodeObject关键属性:
type:数据类型(二维码,条形码等码类型);
stringValue:二维码或条形码的值;
bounds:二维码在源视频流对应的帧图像中的位置和大小。
corners:二维码的四个角的点坐标。
其中bounds和corners的基准坐标系是视频源中对应的帧画面坐标系。
我们主要用corners来计算二维码在videoPreviewLayer上显示的位置及对应的视图坐标系下的frame。
然后添加透明遮罩层在对应的位置添加按钮及点击事件。

计算位置的具体步骤为:

评论区有说系统自带方法是可以直接计算frame的:transformedMetadataObjectForMetadataObject方法
有验证过的可以在评论区补充~

//在这个方法里面:
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection{
    AVMetadataObject *newObj = [connection.videoPreviewLayer transformedMetadataObjectForMetadataObject:obj];
    //newObj.bounds 就是转为app坐标系的了。
}
//connection

以上方法可用的话,下面这步骤可以不用看了。
1,获取差值。

初始坐标系,以iphoneXS流分辨率720*1280为例和屏幕尺寸有差值

2,交换X,Y轴。

3,翻转X轴。

4,带入差值计算(若是其它填充方式则需要修改差值计算,有可能需要增加补值)

此时4个角点坐标已转换完毕,但是二维码不一定与屏幕角度相同,通过角点计算出二维码的长方形范围坐标作为二维码frame,并在其中心点添加按钮:
左边为理想情况,右边为实际情况,红色虚线frame即为二维码坐标

具体可看方法:-(CGRect)makeFrameWithCodeObject:(AVMetadataMachineReadableCodeObject *)objc;

2,图片选择扫描:

图片扫描返回的数据为:CIQRCodeFeature主要参数:
messageString:码值;
bounds:码位置
其中bounds是二维码在图片坐标系上的位置,单位是像素,坐标系方向受图片方向影响。
关于图片方向:
uiimage方向属性imageOrientation:

typedef NS_ENUM(NSInteger, UIImageOrientation) {
    UIImageOrientationUp,            // default orientation
    UIImageOrientationDown,          // 180 deg rotation
    UIImageOrientationLeft,          // 90 deg CCW
    UIImageOrientationRight,         // 90 deg CW
    UIImageOrientationUpMirrored,    // as above but image mirrored along other axis. horizontal flip
    UIImageOrientationDownMirrored,  // horizontal flip
    UIImageOrientationLeftMirrored,  // vertical flip
    UIImageOrientationRightMirrored, // vertical flip
};

手机展示图片时会受图片方向影响,比如无论是横持还是竖持甚至倒持手机拍摄一个“↑“,在相册展示时箭头都是朝上的。我们在imageView展示时也是一样。但是二维码识别返回的数据中:
靠近音量调节的角为原点(0,0)。长边为X轴,短边为Y轴,二维码的origin为靠近原点的角
所以需要坐标系转换。
默认图片拍摄的坐标系为手机朝左横持为下图的样子(即图片方向为UIImageOrientationUp的图片,是以下图手持方向拍摄的)

默认UIImageOrientationUp时的照片方向,绿点为frame.origin

正常竖屏展示的样子(正常开发时的坐标系)及二维码frame.origin点为:(UIImageOrientationUp的图片,在app内展示的样子,及我们需要的origin点的位置)


与上一张图做比较

对比后,我们应该要做Y轴反转和比例缩放计算
y轴翻转计算:图片的H-二维码的H-二维码的y值。
在计算图片缩放比列后就可以得出在正常坐标系下二维码在图片的位置。


其它几种拍照下的坐标系示例:


左边为常用的拍照场景,UIImageOrientationRight。右边为UIImageOrientationDown

可以看出:
UIImageOrientationRight方向的图片需要XY交换,
UIImageOrientationDown方向的需要翻转x值。
其他属性可以类推。

另外带镜像属性的图片暂时没有找到,未做测试。

代码部分

.h:

#import <UIKit/UIKit.h>
typedef void(^ScanResultBlock)(NSString* result); //二维码点选操作
typedef void(^ScanOutputs)(NSArray* results);//@[@{@"code":"",@"frame":@""}]

@interface XYQRScanViewController : UIViewController
@property (strong, nonatomic, readonly) AVCaptureVideoPreviewLayer *videoPreviewLayer;
@property (assign, nonatomic, readonly) BOOL isScaning;
@property (copy, nonatomic) ScanOutputs scanOutputs;//可选的实现扫描多个二维码后的预测操作。比如自动选择某个二维码

- (void)startScan;
- (void)stopScan;

//+ (NSArray <NSDictionary *> *)scanWithImage:(UIImage*)image; //暂未实现
- (instancetype)initWithScanResultBlock:(ScanResultBlock)block;//二维码点选操作

@end

.m

.m:

#import "XYQRScanViewController.h"
#import "UIColor+DynamicColor.h"
#import "HDXYScanImageSelectController.h"

#define SCREEN_WIDTH [UIScreen mainScreen].bounds.size.width
#define SCREEN_HEIGHT [UIScreen mainScreen].bounds.size.height
#define kDevice_Is_iPhoneX (SCREEN_WIDTH >= 375.f && SCREEN_HEIGHT >= 812.f)
#define kSafeArea_Top (kDevice_Is_iPhoneX? 44: 20 )

@interface XYQRScanViewController ()<AVCaptureMetadataOutputObjectsDelegate, UIImagePickerControllerDelegate,AVCaptureVideoDataOutputSampleBufferDelegate>

@property (nonatomic, strong) UILabel *promptLabel;

@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UIView *bottomView;

@property (strong, nonatomic) UIView *myScanBGView;
@property (strong, nonatomic) UIImageView *scanRectView, *lineView;
@property (strong, nonatomic) UILabel *tipLabel;
@property (strong, nonatomic) AVCaptureVideoPreviewLayer *videoPreviewLayer;
@property (strong, nonatomic) CIDetector *detector;
@property (strong, nonatomic) UIButton *lightButton;
@property (strong,nonatomic)UIButton *photoButton;
@property (strong,nonatomic)UIButton *codeButton;
@property (strong, nonatomic) UIView *topBg;
@property (nonatomic,assign)CGRect scanRect;
@property (nonatomic,strong)UIView *bottomContentView;
@property (nonatomic,strong)UILabel *cricleLabel;
//@property (nonatomic,strong)MLMSegmentHead *segemetHead;
@property (nonatomic,strong)UIButton *popButton;
@property (assign, nonatomic, readwrite) BOOL isScaning;
@property (nonatomic, copy) void(^scanBlock)(NSString *scanStr);//扫码回调
@end

@implementation XYQRScanViewController

- (instancetype)initWithScanResultBlock:(ScanResultBlock)block
{
    self = [super init];
    if (self) {
        _scanBlock = block;
    }
    return self;
}

- (CIDetector *)detector{
    if (!_detector) {
        _detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:@{ CIDetectorAccuracy : CIDetectorAccuracyHigh }];
    }
    return _detector;
}

- (void)viewDidLoad {
    [super viewDidLoad];
     UIColor *color = [UIColor colorWithDarkModeColor:BlackColor normalColor:HexF7F8FA];
     self.view.backgroundColor = color;
     NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
     
     [nc addObserver:self
            selector:@selector(applicationDidBecomeActive:)
                name:UIApplicationDidBecomeActiveNotification
              object:nil];
     [nc addObserver:self
            selector:@selector(applicationWillResignActive:)
                name:UIApplicationWillResignActiveNotification
              object:nil];
     
     [self configUI];
}
-(void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
     self.navigationController.navigationBar.hidden =  NO;
    [self startScan];
}

-(void)viewWillDisappear:(BOOL)animated{
    [super viewWillDisappear:animated];

}

- (void)setupNavigationBar {
     self.navigationController.navigationBar.translucent = YES;
     [self.navigationController.navigationBar setBackgroundImage:[UIImage createImageWithColor:[UIColor clearColor]] forBarMetrics:UIBarMetricsDefault];
     NSDictionary *textAttributes = @{
                                      NSFontAttributeName: [UIFont boldSystemFontOfSize:18],
                                      NSForegroundColorAttributeName:WhiteColor
                                      };
     [self.navigationController.navigationBar setTitleTextAttributes:textAttributes];
     
     self.navigationItem.title = @"扫码";
     UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom];
     backButton.frame = CGRectMake(0, 0, 40, 40);
     [backButton setImage:[UIImage imageNamed:@"nav_back_w"] forState:UIControlStateNormal];
     [backButton addTarget:self action:@selector(scanBack) forControlEvents:UIControlEventTouchUpInside];
     backButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
     self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
     UIButton *rightButton = [UIButton buttonWithType:UIButtonTypeCustom];
     rightButton.frame = CGRectMake(0, 0, 40, 40);
     [rightButton setTitle:@"相册" forState:UIControlStateNormal];
     [rightButton setTitleColor:WhiteColor forState:UIControlStateNormal];
     rightButton.titleLabel.font = FontRegular(16);
     rightButton.alpha = 0.8;
     [rightButton addTarget:self action:@selector(rightBarButtonItenAction) forControlEvents:UIControlEventTouchUpInside];
     rightButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentRight;
     self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:rightButton];
}
-(void)configUI{
     [self setupNavigationBar];
    _isScaning = YES;
     [self.view.layer addSublayer:self.videoPreviewLayer];
     [self.view addSubview:self.titleLabel];
     [self.view addSubview:self.promptLabel];

    [self.view addSubview:self.myScanBGView];

    [self.view addSubview:self.lineView];

    [self scanLineStartAction];

}

- (void)scanLineStartAction{
    
    [self scanLineStopAction];
    
    CAAnimationGroup *group = [CAAnimationGroup animation];
    CABasicAnimation *scanAnimation = [CABasicAnimation animationWithKeyPath:@"position.y"];
    scanAnimation.fromValue = @(80);
    scanAnimation.toValue = @(SCREEN_HEIGHT-160-95);
    scanAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
    
    CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
    opacityAnimation.fromValue = @(1);
    opacityAnimation.toValue = @(0);
    opacityAnimation.repeatCount = CGFLOAT_MAX;
    opacityAnimation.duration = 0.5;
    opacityAnimation.beginTime = 2;
    
    group.animations = @[scanAnimation,opacityAnimation];
    group.removedOnCompletion = NO;
    group.fillMode = kCAFillModeForwards;
    group.duration  = 2.5;
    group.repeatCount = CGFLOAT_MAX;
    
    [self.lineView.layer addAnimation:group forKey:@"basic"];
}
-(void)scanLineStopAction{
    
    [self.lineView.layer removeAllAnimations];
}

-(void)scanBack{
    if (self.navigationController) {
        [self.navigationController popViewControllerAnimated:YES];
    }
}

- (void)rightBarButtonItenAction {
    WeakSelf(ws);
     
     UIImagePickerController *picker = [UIImagePickerController new];
     picker.delegate = (id)self;
     picker.allowsEditing = NO;
     picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
     picker.modalPresentationStyle = UIModalPresentationFullScreen;
     [ws.navigationController presentViewController:picker animated:YES completion:nil];

}
-(void)dealloc {
    
    [self.videoPreviewLayer removeFromSuperlayer];
    self.videoPreviewLayer = nil;
    [self scanLineStopAction];
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    NSLog(@"%s",__func__);
}
#pragma mark -----public

- (void)playSoundName:(NSString *)name {
    /// 静态库 path 的获取
    NSString *path = [[NSBundle mainBundle] pathForResource:name ofType:nil];
    if (!path) {
        /// 动态库 path 的获取
        path = [[NSBundle bundleForClass:[self class]] pathForResource:name ofType:nil];
    }
    NSURL *fileUrl = [NSURL fileURLWithPath:path];
    
    SystemSoundID soundID = 0;
    AudioServicesCreateSystemSoundID((__bridge CFURLRef)(fileUrl), &soundID);
    AudioServicesAddSystemSoundCompletion(soundID, NULL, NULL, NULL, NULL);
    AudioServicesPlaySystemSound(soundID);
}

- (void)startScan{
    _isScaning = YES;
    [self.videoPreviewLayer.session startRunning];
    [self scanLineStartAction];
}
- (void)stopScan{
    _isScaning = NO;
    [self.videoPreviewLayer.session stopRunning];
    [self scanLineStopAction];
}
-(void)openFlash:(UIButton*)button{
    button.selected = !button.selected;
    if (button.selected) {
        [self turnTorchOn:YES];
    }
    else{
        [self turnTorchOn:NO];
    }
}

- (void)turnTorchOn:(BOOL)on
{
    Class captureDeviceClass = NSClassFromString(@"AVCaptureDevice");
    if (captureDeviceClass != nil) {
        AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
        
        if ([device hasTorch] && [device hasFlash]){
            [device lockForConfiguration:nil];
            if (on) {
                device.torchMode = AVCaptureTorchModeOn;
            } else {

                device.torchMode = AVCaptureTorchModeOff;
            }
            [device unlockForConfiguration];
        }
    }
}
#pragma mark -----Notification
- (void)applicationDidBecomeActive:(UIApplication *)application {
    [self startScan];
}

- (void)applicationWillResignActive:(UIApplication *)application {
    [self stopScan];
}

#pragma mark ------imagepickDelegate
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id> *)info
{
     WeakSelf(weakSelf);
     [picker dismissViewControllerAnimated:YES completion:^{
         [weakSelf handleImageInfo:info];
     }];
}



- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker{
    [picker dismissViewControllerAnimated:YES completion:nil];
    [self startScan];
}

+(UIViewController *)showSelectControllerWithImage:(UIImage *)image dataArray:(NSArray *)array block:(ScanResultBlock)block
{
    HDXYScanImageSelectController *controller = [HDXYScanImageSelectController new];
    controller.image =image;
    controller.tagArray = array;
    controller.scanBlock = block;
    return controller;
}


#pragma mark 图片识别
- (void)handleImageInfo:(NSDictionary *)info{

     [self startScan];
     UIImage *image = [info objectForKey:UIImagePickerControllerEditedImage];
     if (!image){
          image = [info objectForKey:UIImagePickerControllerOriginalImage];
     }
     __block NSString *resultStr = nil;
     __block BOOL haveCode = NO;
//图片识别二维码
     NSArray *features = [self.detector featuresInImage:[CIImage imageWithCGImage:image.CGImage]];
//判断是否识别出二维码
     [features enumerateObjectsUsingBlock:^(CIQRCodeFeature *obj, NSUInteger idx, BOOL *stop) {
        if (obj.messageString.length > 0) {
             resultStr = obj.messageString;
             NSLog(@"%@",resultStr);
             haveCode = resultStr.length>0?YES:NO;
             *stop = YES;
          }
     }];
     [self stopScan];
     NSMutableArray *muchArray = [NSMutableArray new];
     if (haveCode) {
          [features enumerateObjectsUsingBlock:^(CIQRCodeFeature *obj, NSUInteger idx, BOOL *stop) {
               NSMutableDictionary *dic = [NSMutableDictionary new];
               CGRect frame =obj.bounds;
               NSString *frameStr = NSStringFromCGRect(frame);
               [dic setObject:frameStr forKey:@"frame"];
               NSString *code = obj.messageString;
               [dic setObject:code forKey:@"code"];
               [muchArray addObject:dic];
          }];
     }
     if (self.scanOutputs) {
          self.scanOutputs(muchArray);
          return;
     }
     WeakSelf(weakSelf);
     [self.navigationController pushViewController:[XYQRScanViewController showSelectControllerWithImage:image dataArray:muchArray block:^(NSString *result) {
          if (result && result.length>0) {
               if (weakSelf.scanBlock) {
                    [weakSelf.navigationController popViewControllerAnimated:NO];
                    weakSelf.scanBlock(result);
               }
          }
     }] animated:NO];
     return;
}
#pragma mark ------AVCaptureVideoDataOutputSampleBufferDelegate
//弱光识别
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
    
    static BOOL isStop = false;
    if (isStop) return;
    
    isStop = true;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        //0.5秒一次弱光监听
        isStop = false;
    });
    
    CFDictionaryRef metadataDict = CMCopyDictionaryOfAttachments(NULL,sampleBuffer, kCMAttachmentMode_ShouldPropagate);
    NSDictionary *metadata = [[NSMutableDictionary alloc] initWithDictionary:(__bridge NSDictionary*)metadataDict];
    CFRelease(metadataDict);
    NSDictionary *exifMetadata = [[metadata objectForKey:(NSString *)kCGImagePropertyExifDictionary] mutableCopy];
    float brightnessValue = [[exifMetadata objectForKey:(NSString *)kCGImagePropertyExifBrightnessValue] floatValue];

    if (brightnessValue < 0 && !self.lightButton.selected && self.lightButton.isHidden) {
        self.lightButton.hidden = NO;
    }
    if (brightnessValue > 0 && !self.lightButton.selected && !self.lightButton.isHidden) {
        self.lightButton.hidden = YES;
    }
}

#pragma mark ------AVCaptureMetadataOutputObjectsDelegate,扫描结果输出流代理

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection{
    //判断是否有数据,是否是二维码数据
    NSMutableArray *muchArray = [NSMutableArray new];
    if (metadataObjects.count > 0) {
        [metadataObjects enumerateObjectsUsingBlock:^(AVMetadataMachineReadableCodeObject *obj, NSUInteger idx, BOOL *stop) {
            if ([obj.type isEqualToString:AVMetadataObjectTypeQRCode]) {
                [muchArray addObject:obj];
            }
        }];
        if (muchArray.count>0) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [self analyseResultAry:muchArray];
            });
        }
    }

}
- (void)analyseResultAry:(NSArray *)resultArray{
     if (!_isScaning) {
          return;
     }
     [self stopScan];
     [self playSoundName:@"SGQRCode.bundle/sound.caf"];
     NSMutableArray *muchArray = [NSMutableArray new];
     [resultArray enumerateObjectsUsingBlock:^(AVMetadataMachineReadableCodeObject *result, NSUInteger idx, BOOL *stop) {
          NSMutableDictionary *dic = [NSMutableDictionary new];
          NSString *code = result.stringValue;
          [dic setObject:code forKey:@"code"];
        
          CGRect frame = [self makeFrameWithCodeObject: result];
          NSString *frameStr = NSStringFromCGRect(frame);
          [dic setObject:frameStr forKey:@"frame"];
          [muchArray addObject:dic];
     }];
     
     if (muchArray.count>=1) {
          UIImage *image;
          WeakSelf(weakSelf);
          if (self.scanOutputs) {
               self.scanOutputs(muchArray);
               return;
          }

          UIViewController *viewController = [XYQRScanViewController showSelectControllerWithImage:image dataArray:muchArray block:^(NSString *result) {
               weakSelf.popButton.hidden = NO;
               weakSelf.lineView.hidden = NO;
               if (weakSelf.scanBlock) {
                    if (result) {
                         [weakSelf.navigationController popViewControllerAnimated:YES];
                         weakSelf.scanBlock(result);
                    }else{
                         [weakSelf startScan];
                    }
               }
          }];
          viewController.modalPresentationStyle =UIModalPresentationOverFullScreen;
          [self.navigationController presentViewController:viewController animated:NO completion:nil];
          self.popButton.hidden = YES;
          self.lineView.hidden = YES;
          return;
     }
     [self startScan];
}

/*
 AVMetadataMachineReadableCodeObject,输出的点位坐标是其在原始数据流上的坐标,与屏幕视图坐标不一样,(坐标系,值都会有差别)
     将坐标值转为屏幕显示的图像视图(self.videoPreviewLayer)上的坐标值
 */
-(CGRect)makeFrameWithCodeObject:(AVMetadataMachineReadableCodeObject *)objc
{
     //将二维码坐标转化为扫码控件输出视图上的坐标
     CGSize isize = CGSizeMake(720.0, 1280.0); // 尺寸可以考虑不要写死,当前设置的是captureSession.sessionPreset = AVCaptureSessionPreset1280x720;
//     CGSize isize = self.view.frame.size; //扫码控件的输出尺寸,
     float Wout = 0.00;
     float Hout = 0.00;
     BOOL wMore = YES;
    /*取分辨率与输出的layer尺寸差,
     此处以AVLayerVideoGravityResizeAspectFill填充方式为例,判断扫描的范围更宽还是更长,并计算出超出部分的尺寸,后续计算减去这部分。
     如果是其它填充方式,计算方式不一样(比如AVLayerVideoGravityResizeAspect,则计算计算留白的尺寸,并后续补足这部分)
     */
     if (isize.width/isize.height > self.videoPreviewLayer.bounds.size.width/self.videoPreviewLayer.bounds.size.height) {
          //当更宽时,计算扫描的坐标x为0 的点比输出视图的0点差多少(输出视图为全屏时,即屏幕外有多少)
          wMore = YES;
          Wout = (isize.width/isize.height)* self.videoPreviewLayer.bounds.size.height;
          Wout = Wout - self.videoPreviewLayer.bounds.size.width;
          Wout = Wout/2;
     }else{
          // 当更长时,计算y轴超出多少。
          wMore = NO;
          Hout = (isize.height/isize.width)* self.videoPreviewLayer.bounds.size.width;
          Hout = Hout  - self.videoPreviewLayer.bounds.size.height;
          Hout = Hout/2;
     }
    
     CGPoint point1 = CGPointZero;
     CGPoint point2 = CGPointZero;
     CGPoint point3 = CGPointZero;
     CGPoint point4 = CGPointZero;
     /*
      源坐标系下frame和角点,都是比例值,即源视频流尺寸下的百分比值。
      例子:frame :(x = 0.26720550656318665, y = 0.0014114481164142489), size = (width = 0.16406852006912231, height = 0.29584407806396484))
      objc.corners:{0.26823519751360592, 0.29203594744002659}
                    {0.4312740177700658, 0.29725551905635411}
                    {0.4294213439632073, 0.012761536345436197}
                    {0.26720551457151021, 0.0014114481640513654}
      */
     CGRect frame = objc.bounds;//在源坐标系的frame,
     NSArray *array = objc.corners;//源坐标系下二维码的角点
     CGPoint P = frame.origin;
     CGSize S = frame.size;
     
     //获取点
     for (int n = 0; n< array.count; n++) {
          
          CGPoint point = CGPointZero;
          CFDictionaryRef dict = (__bridge CFDictionaryRef)(array[n]);
          CGPointMakeWithDictionaryRepresentation(dict, &point);
          NSLog(@"二维码角点%@",NSStringFromCGPoint(point));
          //交换xy轴
          point.x = point.y +  point.x;
          point.y = point.x - point.y;
          point.x = point.x - point.y;
          //x轴反转
          point.x = (1-point.x);
          //point乘以比列。减去尺寸差,
          if (wMore) {
               point.x = (point.x * (isize.width/isize.height)* self.videoPreviewLayer.bounds.size.height) - Wout;
               point.y = self.videoPreviewLayer.bounds.size.height *(point.y);
          }else{
               point.x = self.videoPreviewLayer.bounds.size.width *(point.x);
               point.y = (point.y) * (isize.height/isize.width)* self.videoPreviewLayer.bounds.size.width - Hout;
          }
          if (n == 0) {
               point1 = point;
          }
          if (n == 1) {
               point2 = point;
          }
          if (n == 2) {
               point3 = point;
          }
          if (n == 3) {
               point4 = point;
          }
     }
     //通过获取最小和最大的X,Y值,二维码在视图上的frame(前面得到的点不一定是正方形的二维码,也可能是菱形的或者有一定旋转角度的)
     float minX = point1.x;
     minX = minX>point2.x?point2.x:minX;
     minX = minX>point3.x?point3.x:minX;
     minX = minX>point4.x?point4.x:minX;
        
     float minY = point1.y;
     minY = minY>point2.y?point2.y:minY;
     minY = minY>point3.y?point3.y:minY;
     minY = minY>point4.y?point4.y:minY;
     P.x = minX;
     P.y = minY;
        
     float maxX = point1.x;
     maxX = maxX<point2.x?point2.x:maxX;
     maxX = maxX<point3.x?point3.x:maxX;
     maxX = maxX<point4.x?point4.x:maxX;
        
     float maxY = point1.y;
     maxY = maxY<point2.y?point2.y:maxY;
     maxY = maxY<point3.y?point3.y:maxY;
     maxY = maxY<point4.y?point4.y:maxY;
        
     S.width = maxX - minX;
     S.height = maxY - minY;

     //y轴坐标方向调整
     CGRect QRFrame = CGRectMake(P.x , P.y  , S.width, S.width);
     return QRFrame;
}

#pragma mark 懒加载

- (UILabel *)titleLabel {
    if (!_titleLabel) {
        _titleLabel = [[UILabel alloc] init];
        _titleLabel.backgroundColor = [UIColor clearColor];
        CGFloat promptLabelX = 0;
        CGFloat promptLabelY = 0.12 * SCREEN_HEIGHT;
        CGFloat promptLabelW = SCREEN_WIDTH;
        CGFloat promptLabelH = 25;
        _titleLabel.frame = CGRectMake(promptLabelX, promptLabelY, promptLabelW, promptLabelH);
        _titleLabel.textAlignment = NSTextAlignmentCenter;
        _titleLabel.font = FontRegular(16);
        _titleLabel.textColor = WhiteColor;
        _titleLabel.alpha = 0.8;
        _titleLabel.text = @"扫描二维码";
    }
    return _titleLabel;
}

- (UIView *)bottomView {
    if (!_bottomView) {
        _bottomView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT - 0)];
        _bottomView.backgroundColor = [BlackColor colorWithAlphaComponent:0.6];
    }
    return _bottomView;
}

-(UIImageView *)lineView{
    
    if (!_lineView){
         /// 静态库 url 的获取, 扫描线暂时用的原第三方库的图片,
         NSURL *url = [[NSBundle mainBundle] URLForResource:@"SGQRCode" withExtension:@"bundle"];
         if (!url) {
             /// 动态库 url 的获取
             url = [[NSBundle bundleForClass:[self class]] URLForResource:@"SGQRCode" withExtension:@"bundle"];
         }
         NSBundle *bundle = [NSBundle bundleWithURL:url];
         
         UIImage *image = [UIImage imageNamed:@"QRCodeScanLine" inBundle:bundle compatibleWithTraitCollection:nil];
         if (!image) {
             image = [UIImage imageNamed:@"QRCodeScanLine"];
         }

//        UIImage *lineImage = [UIImage imageNamed:@"QRCodeScanLine@2x.png"];
        CGFloat lineHeight = 30;
        CGFloat lineWidth = CGRectGetWidth(self.view.frame);
        _lineView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 40, lineWidth, lineHeight)];
        _lineView.contentMode = UIViewContentModeScaleToFill;

        _lineView.image = image;

    }
    return _lineView;
}

-(AVCaptureVideoPreviewLayer *)videoPreviewLayer{
     
     if (!_videoPreviewLayer) {
         NSError *error;
         AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; //相机的硬件接口
         AVCaptureDeviceInput *input    = [AVCaptureDeviceInput deviceInputWithDevice:captureDevice error:&error];  // 用于流的捕获,输入流
         if (!input) {
              NSLog(@"%@", error.localizedDescription);
              [self.navigationController popViewControllerAnimated:YES];
              return nil;
         }else{
              //设置视频会话(时域)流
              AVCaptureSession *captureSession = [AVCaptureSession new];
              [captureSession addInput:input]; //设置输入流
              captureSession.sessionPreset = AVCaptureSessionPreset1280x720; //输出的分辨率
              //创建流输出
              AVCaptureMetadataOutput *captureMetadataOutput = [[AVCaptureMetadataOutput alloc] init];
              [captureMetadataOutput setMetadataObjectsDelegate:self queue:dispatch_queue_create("ease_capture_queue",NULL)];//设置输出代理,及处理线程(输出类型为上面设置的类型,比如二维码或人脸数据或者别的等等)
              [captureSession addOutput:captureMetadataOutput];//添加流输出到session
              //设置输出类型:AVMetadataObjectTypeQRCode,//其它功能比如人脸扫描等可以自行研究
              if (![captureMetadataOutput.availableMetadataObjectTypes containsObject:AVMetadataObjectTypeQRCode]) {
                   NSLog(NSLocalizedString(@"摄像头不支持扫描二维码!", nil));
                   [self.navigationController popViewControllerAnimated:YES];
              }else{
                   [captureMetadataOutput setMetadataObjectTypes:captureMetadataOutput.availableMetadataObjectTypes];
              }
              
              
            
              captureMetadataOutput.rectOfInterest = CGRectMake(0,0,1,1);//设置扫描区域。。默认是手机头向左的横屏坐标系(逆时针旋转90度),左上角为(0,0)点.坐标系↓→
              // 弱光识别监听
              AVCaptureVideoDataOutput *buffer = [[AVCaptureVideoDataOutput alloc] init];
              [buffer setSampleBufferDelegate:self queue:dispatch_get_main_queue()];

              if ([captureSession canAddOutput:buffer]){
                   [captureSession addOutput:buffer];
              }
              //将捕获的数据流展现出来
              _videoPreviewLayer = [AVCaptureVideoPreviewLayer layerWithSession:captureSession];
              [_videoPreviewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill]; //等比例填充,超出部分会被剪裁(这也就是为什么有些扫码屏幕内只显示了一半也能完成扫描。其实摄像头已经捕获了)(AVLayerVideoGravityResizeAspect全部显示,少的部分有留白)
              [_videoPreviewLayer setFrame:self.view.bounds];
              
              
//              //以下是修改扫描区域为屏幕内的方案,修改参数可以指定范围内扫描
//              CGSize isize = CGSizeMake(720.0, 1280.0); //当前设置的是captureSession.sessionPreset = AVCaptureSessionPreset1280x720;
//              float Wout = 0.00;
//              float Hout = 0.00;
//              BOOL wMore = YES;
//             //取分辨率与输出的layer尺寸差,首先判断目扫描的范围更宽还是更长,
//              if (isize.width/isize.height > self.view.bounds.size.width/self.view.bounds.size.height) {
//                   //当更宽时,计算扫描的坐标x为0 的点比输出视图的0点差多少(输出视图为全屏时,即屏幕外有多少)
//                   wMore = YES;
//                   Wout = (isize.width/isize.height)* self.videoPreviewLayer.bounds.size.height;
//                   Wout = Wout - self.videoPreviewLayer.bounds.size.width;
//                   Wout = Wout/2;
//              }else{
//                   // 当更长时,计算y轴超出多少。
//                   wMore = NO;
//                   Hout = (isize.height/isize.width)* self.videoPreviewLayer.bounds.size.width;
//                   Hout = Hout  - self.videoPreviewLayer.bounds.size.height;
//                   Hout = Hout/2;
//              }
//              if (wMore) {
//                   captureMetadataOutput.rectOfInterest = CGRectMake(0,Wout/720.0,1,(720.0-2*Wout)/720.0);
//              }else{
//                   captureMetadataOutput.rectOfInterest = CGRectMake(Hout/1280.0,0,(1280.0-2*Hout)/1280.0,0);
//              }
//              captureMetadataOutput.rectOfInterest = CGRectMake(0,0,0.5,0.5);
         }
     }
     return _videoPreviewLayer;
}

@end

展示点选按钮

.h:

#import <UIKit/UIKit.h>

@interface HDXYScanImageSelectController : UIViewController
@property (nonatomic, copy) void(^scanBlock)(NSString *scanStr);//扫码点选回调
/*
 选择图片扫码结果展示:传入image。
 摄像头扫码结果展示:image不需要传入,后面会自动生成透明图片尺寸与扫码页面视频流的展示大小一致,如果扫码页面视频流不是全图则需要修改这部分逻辑
 */
@property(nonatomic,copy)UIImage *image;
@property(nonatomic,copy)NSArray *tagArray;

@end

.m:


#import "HDXYScanImageSelectController.h"

#define maxBtnSize 54
#define minBtnSize 18

#define SCREEN_WIDTH [UIScreen mainScreen].bounds.size.width
#define SCREEN_HEIGHT [UIScreen mainScreen].bounds.size.height
#define kDevice_Is_iPhoneX (SCREEN_WIDTH >= 375.f && SCREEN_HEIGHT >= 812.f)
#define kSafeArea_Top (kDevice_Is_iPhoneX? 44: 20 )
#define kSafeArea_Bottom ((kDevice_Is_iPhoneX? 34: 0))

@interface HDXYScanImageSelectController ()
@property (nonatomic,strong)UIButton *popButton;
@property (nonatomic,strong)UILabel *downLable;
@property (nonatomic,strong)UIView *bigDarkView;
@property BOOL noImage;
@end

@implementation HDXYScanImageSelectController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self.view addSubview:self.popButton];
    [self.popButton mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(self.view).offset(25);
        make.top.equalTo(self.view).offset(12.5+(kDevice_Is_iPhoneX?44:20));
    }];
    // Do any additional setup after loading the view.
}

-(void)viewWillAppear:(BOOL)animated
{
    self.view.backgroundColor = [UIColor blackColor];
    self.navigationController.navigationBar.hidden =  YES;
    self.noImage = NO;
    if (!self.image) {
        self.noImage = YES;
        self.image = [self createImageWithColor:[UIColor colorWithRed:0.1 green:0.1 blue:0.1 alpha:0.5]];
        self.view.backgroundColor = [UIColor clearColor];
    }

    UIImageView *imageView = [self buildImageView];
    
    [self.view addSubview:imageView];
    if (self.tagArray && self.tagArray.count>0) {
        [self.tagArray enumerateObjectsUsingBlock:^(NSDictionary *dataDic, NSUInteger idx, BOOL *stop) {
            UIButton *btn = [self buildBtnWithDic:dataDic andNumber:idx];
            [imageView addSubview:btn];
        }];

        [self.view addSubview:self.downLable];
        [self.downLable mas_makeConstraints:^(MASConstraintMaker *make) {
                make.centerX.mas_equalTo(self.view.mas_centerX);
                make.bottom.mas_offset(-40-kSafeArea_Bottom);
        }];
        [self.view bringSubviewToFront:self.popButton];
    }else{
        [self.view addSubview:self.bigDarkView];
        [self.bigDarkView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.left.right.top.bottom.mas_equalTo(self.view);
        }];
    }
    [self.view bringSubviewToFront:self.popButton];
    [super viewWillAppear:animated];
}
-(void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
   //只有一个二维码时,展示一下就自动选择
    if (self.tagArray.count == 1) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSDictionary *dataDic = self.tagArray[0];
            [self doingWithMessage:dataDic[@"code"]];
        });
    }
}


-(UIButton *)buildBtnWithDic:(NSDictionary *)dic andNumber:(NSInteger)number
{
     CGSize startImageSize = [HDXYScanImageSelectController getSizeWithImage:self.image];
     CGFloat fixelW = startImageSize.width;
     CGFloat fixelH = startImageSize.height;
    //计算UIimageView的尺寸,
     if (fixelH/fixelW > self.view.bounds.size.height/self.view.bounds.size.width) {
          fixelW = (fixelW/fixelH) * self.view.bounds.size.height;
          fixelH = self.view.bounds.size.height;
     }else{
          fixelH = (fixelH/fixelW) * self.view.bounds.size.width;
          fixelW = self.view.bounds.size.width;
     }
     float scale =  fixelW/startImageSize.width;
     CGRect btnFrame =  CGRectFromString(dic[@"frame"]);
     //根据图片方向调整frame
     if (!self.noImage) {
          btnFrame = [HDXYScanImageSelectController exChangeFrame:btnFrame withImage:self.image];
     }
     //坐标等比缩放
     btnFrame = CGRectMake(btnFrame.origin.x*scale, btnFrame.origin.y *scale, btnFrame.size.width *scale, btnFrame.size.height *scale);

    //按钮初步大小调整
    btnFrame = CGRectMake(btnFrame.origin.x + btnFrame.size.width/4,btnFrame.origin.y + btnFrame.size.height/4, btnFrame.size.width/2, btnFrame.size.height/2);
    if (btnFrame.size.width != btnFrame.size.height) {
        float w = btnFrame.size.width;
        float h = btnFrame.size.height;
        btnFrame = CGRectMake(btnFrame.origin.x, btnFrame.origin.y + (h-w)/2, w, w);
    }
    if (btnFrame.size.width > maxBtnSize) {
        float w = btnFrame.size.width;
        float h = btnFrame.size.height;
        btnFrame = CGRectMake(btnFrame.origin.x + (w-maxBtnSize)/2, btnFrame.origin.y + (h-maxBtnSize)/2, maxBtnSize, maxBtnSize);
    }
    if (btnFrame.size.width < minBtnSize) {
        float w = btnFrame.size.width;
        float h = btnFrame.size.height;
        btnFrame = CGRectMake(btnFrame.origin.x + (w-minBtnSize)/2, btnFrame.origin.y + (h-minBtnSize)/2, minBtnSize, minBtnSize);
    }
    btnFrame.size.width = btnFrame.size.height;
    UIButton *btn = [[UIButton alloc]init];
    btn.frame = btnFrame;

    [btn setBackgroundImage:[UIImage imageNamed:@"scanMany"] forState:UIControlStateNormal];
     btn.backgroundColor = [UIColor yellowColor];
    btn.alpha = 0.99;
    btn.tag = number;
    btn.clipsToBounds = YES;
    [btn addTarget:self action:@selector(clickBtn:) forControlEvents:UIControlEventTouchUpInside];
    [btn.layer addAnimation:[self opacityForever_Animation:0.7] forKey:nil];
    return btn;
}


-(void)clickBtn:(UIButton *)btn
{
    NSInteger number = btn.tag;
    NSString *messageStr = self.tagArray[number][@"code"];
    [self doingWithMessage:messageStr];
    
}

-(void)clickBigBackView:(id)sender
{
    [self doingWithMessage:nil];
}
-(void)doingWithMessage:(NSString *)msg
{
    if (self.navigationController) {
        [self.navigationController popViewControllerAnimated:YES];
        if (self.scanBlock) {
            self.scanBlock(msg);
        }
    }else{
        [self dismissViewControllerAnimated:NO completion:^{
            if (self.scanBlock) {
                self.scanBlock(msg);
            }
        }];
    }
}

#pragma mark === 永久闪烁的动画 ======
-(CABasicAnimation *)opacityForever_Animation:(float)time
{
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
    animation.fromValue = [NSNumber numberWithFloat:0.99f];
    animation.toValue = [NSNumber numberWithFloat:0.3f];
    animation.autoreverses = YES;
    animation.duration = time;
    animation.repeatCount = MAXFLOAT;
    animation.removedOnCompletion = NO;
    animation.fillMode = kCAFillModeForwards;
    animation.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];///没有的话是均匀的动画。
    return animation;
}


+(CGSize)getSizeWithImage:(UIImage *)image
{
    CGFloat fixelW;
    CGFloat fixelH;
    fixelW = CGImageGetWidth(image.CGImage);
    fixelH = CGImageGetHeight(image.CGImage);
    if (image.imageOrientation == UIImageOrientationLeft ||
        image.imageOrientation == UIImageOrientationRight ||
        image.imageOrientation == UIImageOrientationLeftMirrored ||
        image.imageOrientation == UIImageOrientationRightMirrored) {
        
        fixelW = CGImageGetHeight(image.CGImage);
        fixelH = CGImageGetWidth(image.CGImage);
    }
    
    return CGSizeMake(fixelW, fixelH);
}
//根据图片方向调整二维码坐标,图片方向根据拍摄时手机方向而不同
+(CGRect)exChangeFrame:(CGRect)frame withImage:(UIImage *)image
{
     CGFloat fixelW = CGImageGetWidth(image.CGImage);
     CGFloat fixelH = CGImageGetHeight(image.CGImage);
     CGFloat x = frame.origin.x;
     CGFloat y = frame.origin.y;
     CGFloat w = frame.size.width;
     CGFloat h = frame.size.height;
     if (image.imageOrientation == UIImageOrientationUp){
          frame.origin.y = fixelH-y-h;
     }else if (image.imageOrientation == UIImageOrientationLeft){
          //方向逆时针旋转90°拍摄,已实现
          frame.origin.x = fixelH-y-h;
          frame.origin.y = fixelW-x-w;
     }else if (image.imageOrientation == UIImageOrientationRight){
          //顺时针旋转90度拍摄,已实现
          frame.origin.x = y;
          frame.origin.y = x;
     }else if (image.imageOrientation == UIImageOrientationDown){
          //旋转180度拍摄的
          frame.origin.x = fixelW-x-w;
          frame.origin.y = y;
     }else if (image.imageOrientation == UIImageOrientationUpMirrored){
          //左右镜像,已实现未验证
          frame.origin.x = fixelW -x-w;
          frame.origin.y = fixelH-y-h;
        
     }else if (image.imageOrientation == UIImageOrientationDownMirrored){
          //翻转180度后镜像,已实现未验证
          frame.origin.y = y;
          frame.origin.x = x;
     }else if (image.imageOrientation == UIImageOrientationRightMirrored){
          //表示图片被顺时针翻转90°后的镜面图像,已实现未验证
          frame.origin.x = fixelH -y-h;
          frame.origin.y = x;
     }else if (image.imageOrientation == UIImageOrientationLeftMirrored){
          //坐标系逆时针旋转90°并镜像,已实现未验证
          frame.origin.x = y;
          frame.origin.y = fixelW-x-w;
     }
     return frame;
}
-(UIImageView *)buildImageView
{
    CGFloat fixelW = [HDXYScanImageSelectController getSizeWithImage:self.image].width;
    CGFloat fixelH = [HDXYScanImageSelectController getSizeWithImage:self.image].height;
    if (fixelH/fixelW > self.view.bounds.size.height/self.view.bounds.size.width) {
        fixelW = (fixelW/fixelH) * self.view.bounds.size.height;
        fixelH = self.view.bounds.size.height;
    }else{
        fixelH = (fixelH/fixelW) * self.view.bounds.size.width;
        fixelW = self.view.bounds.size.width;
    }
    UIImageView *imageView = [[UIImageView alloc]initWithImage:self.image];
    imageView.bounds = CGRectMake(0, 0, fixelW, fixelH);
    imageView.center = self.view.center;
    imageView.contentMode = UIViewContentModeScaleAspectFit;
    imageView.userInteractionEnabled = YES;
    return imageView;
}
-(UIButton *)popButton{
    if (!_popButton) {
        _popButton = [[UIButton alloc]init];
//        [_popButton setImage:[UIImage imageNamed:@"scan_closed_icon"] forState:UIControlStateNormal];
        [_popButton setTitle:@"取消" forState:UIControlStateNormal];
         [_popButton addTarget:self action:@selector(clickBigBackView:) forControlEvents:UIControlEventTouchUpInside];

    }
    return _popButton;
}
-(UILabel *)downLable
{
    if (!_downLable) {
        _downLable = [UILabel new];
        _downLable.text = @"轻触小蓝点,打开页面";
        [_downLable setTextColor:[UIColor whiteColor]];
        [_downLable setBackgroundColor:[UIColor clearColor]];
        _downLable.textAlignment = NSTextAlignmentCenter;
    }
    return _downLable;
}
//颜色生成image
-(UIImage*) createImageWithColor:(UIColor*) color
{

    CGRect rect=CGRectMake(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT);
    UIGraphicsBeginImageContext(rect.size);
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetFillColorWithColor(context, [color CGColor]);
    CGContextFillRect(context, rect);
    UIImage *theImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return theImage;
}
-(UIView *)bigDarkView
{
     if (!_bigDarkView) {
          _bigDarkView = [UIView new];
          _bigDarkView.backgroundColor = [UIColor colorWithRed:0.3 green:0.3 blue:0.3 alpha:0.5];
          UITapGestureRecognizer * tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(clickBigBackView:)];
          [tapGesture setNumberOfTapsRequired:1];
          [_bigDarkView addGestureRecognizer:tapGesture];
          UILabel *oneLabel = [UILabel new];
          oneLabel.text = @"未发现二维码";
          oneLabel.textColor = [UIColor whiteColor];
          oneLabel.font = [UIFont fontWithName:@"FontWeightStyleRegular" size:17.0];
          oneLabel.textAlignment = NSTextAlignmentCenter;
          [_bigDarkView addSubview:oneLabel];
          [oneLabel mas_makeConstraints:^(MASConstraintMaker *make) {
               make.left.right.mas_equalTo(_bigDarkView);
               make.height.mas_offset(20);
               make.centerY.mas_equalTo(_bigDarkView.mas_centerY).mas_offset(-20);
          }];
          UILabel *twoLabel = [UILabel new];
          twoLabel.text = @"轻触屏幕继续扫描";
          twoLabel.textColor = [UIColor whiteColor];
          twoLabel.font = [UIFont fontWithName:@"FontWeightStyleRegular" size:14.0];;
          twoLabel.textAlignment = NSTextAlignmentCenter;
          [_bigDarkView addSubview:twoLabel];
          [twoLabel mas_makeConstraints:^(MASConstraintMaker *make) {
               make.left.right.mas_equalTo(_bigDarkView);
               make.height.mas_offset(20);
               make.centerY.mas_equalTo(_bigDarkView.mas_centerY);
          }];
     }
     return _bigDarkView;
}
@end

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,165评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,720评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,849评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,245评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,596评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,747评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,977评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,708评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,448评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,657评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,141评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,493评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,153评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,890评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,799评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,685评论 2 272

推荐阅读更多精彩内容