Commit 7474ee25 authored by jz's avatar jz

add controllers

parent 5249babb
//
// GMCameraFaceView.swift
// Gengmei
//
// Created by 刘鹿杰 on 2019/8/17.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
import Foundation
class GMCameraFaceView: GMView {
//捕获设备,通常是前置摄像头,后置摄像头,麦克风(音频输入)
var device: AVCaptureDevice!
//AVCaptureDeviceInput 代表输入设备,他使用AVCaptureDevice 来初始化
fileprivate var input: AVCaptureDeviceInput!
//当启动摄像头开始捕获输入
fileprivate var output: AVCaptureMetadataOutput!
var ImageOutPut: AVCaptureStillImageOutput!
//session:由他把输入输出结合在一起,并开始启动捕获设备(摄像头)
var session: AVCaptureSession!
//图像预览层,实时显示捕获的图像
fileprivate var previewLayer: AVCaptureVideoPreviewLayer!
// 创建中间动画View
fileprivate var animationView: NewFaceScanAnimationView!
// 拍照按钮
fileprivate var shotButton: GMButton!
// 选择相册按钮
fileprivate var albumButton: UIButton!
override func setup() {
customCamera()
setupSubviews()
}
func customCamera() {
if !self.canUseCamera() {
return
}
if Platform.isSimulator {
return
}
// 使用AVMediaTypeVideo 指明self.device代表视频,默认使用后置摄像头进行初始化
self.device = AVCaptureDevice.default(for: AVMediaType.video)
// 尝试获取前置摄像头
for device in AVCaptureDevice.devices(for: AVMediaType.video) where (device as AnyObject).position == .front {
self.device = device
break
}
//使用设备初始化输入
do {
self.input = try AVCaptureDeviceInput(device: self.device)
} catch _ {
}
//生成输出对象
self.output = AVCaptureMetadataOutput()
self.ImageOutPut = AVCaptureStillImageOutput()
//生成会话,用来结合输入输出
self.session = AVCaptureSession()
if self.session.canSetSessionPreset(AVCaptureSession.Preset.hd1280x720) {
self.session.sessionPreset = AVCaptureSession.Preset.hd1280x720
}
if self.session.canAddInput(self.input) {
self.session.addInput(self.input)
}
if self.session.canAddOutput(self.ImageOutPut) {
self.session.addOutput(self.ImageOutPut)
}
//使用self.session,初始化预览层,self.session负责驱动input进行信息的采集,layer负责把图像渲染显示
self.previewLayer = AVCaptureVideoPreviewLayer(session: self.session)
self.previewLayer.frame = CGRect(x: 0, y: 0, width: Constant.screenWidth, height: Constant.screenHeight)
self.previewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
self.layer.insertSublayer(self.previewLayer, at: 0)
//开始启动
self.session.startRunning()
do {
try device.lockForConfiguration()
} catch _ {
print("lockForConfiguration failed")
}
if device.isFlashModeSupported(.off) {
device.flashMode = .off
}
//自动白平衡
if device.isWhiteBalanceModeSupported(.autoWhiteBalance) {
device.whiteBalanceMode = .autoWhiteBalance
}
device.unlockForConfiguration()
}
// 改变照相机拍摄方向
func changeCamera() {
if !canUseCamera() {
return
}
let cameraCount = AVCaptureDevice.devices(for: AVMediaType.video).count
if cameraCount <= 1 {
return
}
var newCamera: AVCaptureDevice?
var newInput: AVCaptureDeviceInput?
if input.device.position == .front {
newCamera = self.cameraWithPosition(.back)
// navigationBar.nearRightButton.isHidden = false
} else {
newCamera = self.cameraWithPosition(.front)
// navigationBar.nearRightButton.isHidden = true
}
do {
newInput = try AVCaptureDeviceInput(device: newCamera!)
} catch let error {
print("toggle carema failed, error = \(error)")
}
if newInput != nil {
session.beginConfiguration()
session.removeInput(input)
if session.canAddInput(newInput!) {
session.addInput(newInput!)
input = newInput
device = newCamera
} else {
session.addInput(self.input)
}
session.commitConfiguration()
}
}
// 是否可以使用照相机
func canUseCamera() -> Bool {
let status = AVCaptureDevice.authorizationStatus(for: AVMediaType.video)
if status == .denied {
return false
} else if status == .notDetermined {
AVCaptureDevice.requestAccess(for: AVMediaType.video, completionHandler: { (result) in
if result {
// uiThread {
self.customCamera()
// }
}
})
return false
} else {
return true
}
}
func cameraWithPosition(_ position: AVCaptureDevice.Position) -> AVCaptureDevice? {
let devices = AVCaptureDevice.devices(for: AVMediaType.video)
for device in devices where (device as AnyObject).position == position {
return device
}
return nil
}
@objc func image(_ image: UIImage, didFinishSavingWithError error: NSError?, contextInfo: UnsafeMutableRawPointer) {
}
// 设置UI
func setupSubviews() {
// 添加动画View
animationView = NewFaceScanAnimationView(frame: self.bounds)
self.addSubview(animationView)
animationView.beginAnimation()
// 拍照按钮
shotButton = GMButton(type: .custom)
shotButton.setImage(UIImage(named: "NewFaceShotButton"), for: .normal)
shotButton.addTarget(self, action: #selector(NewFaceShotController.shotAction), for: .touchUpInside)
self.addSubview(shotButton)
// 选择照片
albumButton = UIButton(type: .custom)
GMNewFaceTool.latestImage { (image) in
if image == nil {
DispatchQueue.main.async {
self.albumButton.setImage(UIImage(named: "NewFacePhotoNone"), for: .normal)
self.albumButton.setImage(UIImage(named: "NewFacePhotoNone"), for: .highlighted)
}
} else {
DispatchQueue.main.async {
self.albumButton.setImage(image, for: .normal)
self.albumButton.setImage(image, for: .highlighted)
}
}
}
albumButton.addTarget(self, action: #selector(NewFaceShotController.ablumAction), for: .touchUpInside)
albumButton.layer.cornerRadius = 4
albumButton.layer.masksToBounds = true
albumButton.layer.borderWidth = 1
albumButton.layer.borderColor = UIColor.white.cgColor
albumButton.imageView?.contentMode = .scaleAspectFill
self.addSubview(albumButton)
// 设置拍照按钮动画
let centerAnimate = CABasicAnimation(keyPath: "transform.rotation.z")
centerAnimate.toValue = Double.pi * 2.0
centerAnimate.duration = 2.5
centerAnimate.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
centerAnimate.repeatCount = Float(LLONG_MAX)
shotButton.layer.add(centerAnimate, forKey: "rotation")
// 更新布局约束
setupSubviewsConstraints()
}
func setupSubviewsConstraints() {
animationView.snp.makeConstraints { (make) in
make.edges.equalTo(UIEdgeInsets.zero)
}
shotButton.snp.makeConstraints { (make) in
make.bottom.equalTo(-52 - UIView.safeAreaInsetsBottom)
make.centerX.equalToSuperview()
make.size.equalTo(CGSize(width: 70, height: 70))
}
albumButton.snp.makeConstraints { (make) in
make.centerY.equalTo(shotButton)
make.centerX.equalTo(shotButton).offset(-Constant.screenWidth*0.25-12)
make.size.equalTo(CGSize(width: 46, height: 46))
}
}
}
//
// GMGeneMemoryAnimationViewController.h
// Gengmei
//
// Created by 朱璇 on 2019/12/6.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@interface GMGeneMemoryAnimationViewController : WMBaseViewController
@property (nonatomic, strong) UIImage *image;
@property (nonatomic, assign) NSInteger gender;
@end
NS_ASSUME_NONNULL_END
//
// GMGeneMemoryAnimationViewController.m
// Gengmei
//
// Created by 朱璇 on 2019/12/6.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMGeneMemoryAnimationViewController.h"
#import "ALFaceInfoViewModel.h"
#import "UIImage+GMExtension.h"
#import "GMFaceV3Util.h"
#import "NSString+jsonData.h"
#import "GMFacePointViewController.h"
#import "Lottie.h"
#define centerPoint CGPointMake(MAINSCREEN_WIDTH / 2, MAINSCREEN_HEIGHT / 2)
#define fromRadius sqrtf((centerPoint.x * centerPoint.x) + (centerPoint.y * centerPoint.y))
#define toRadius (92.0 / 375 * MAINSCREEN_WIDTH)
#define circleRadius (80.0 / 375 * MAINSCREEN_WIDTH)
#define showAnimaitionTime .35
#define hideAnimaitionTime .35
#define circleAnimaitionTime 2
@interface GMGeneMemoryAnimationViewController () {
UIImageView *_bgImage;
UIImageView *_photoImage;
CAShapeLayer *_maskLayer;
}
@property (nonatomic, strong) LOTAnimationView *animationView;
@end
@implementation GMGeneMemoryAnimationViewController
-(void)initController{
[super initController];
self.navigationBar.backgroundColor = UIColor.clearColor;
self.navigationBar.titleLabel.textColor = UIColor.whiteColor;
self.navigationBar.leftIcon = @"NewFaceBack";
self.navigationBar.isShowShadow = NO;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.extraParam = [NSString jsonStringWithObject:@{@"land_tab_name" : @"AI面孔"}];
[self setUp];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self startShowPhotoAnimation];
}
-(void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self removeAnimation];
}
- (void)setUp {
UIImage *newImage = [self.image normalizedImage]; // 防止iOS 13 图片自动旋转90度
// 上传图片 并请求测试结果
__weak __typeof(self)weakSelf = self;
[GMFaceV3Util uploadOneImageToQiNiu:newImage gender:self.gender vc:self successBlock:^(id responseObject) {
[weakSelf startHidePhotoAnimation];
[weakSelf performSelector:@selector(pushToNextVC:) withObject:responseObject afterDelay:.8];
}];
_bgImage = [[UIImageView alloc] init];
_bgImage.image = [UIImage imageNamed:@"gene_loading_bg"];
_bgImage.contentMode = UIViewContentModeScaleAspectFill;
_bgImage.frame = self.view.frame;
[self.view addSubview:_bgImage];
_maskLayer = [CAShapeLayer layer];
_maskLayer.bounds = self.view.bounds;
_maskLayer.fillColor = [UIColor greenColor].CGColor; // Any color but clear will be OK
_maskLayer.path = [UIBezierPath bezierPathWithArcCenter:centerPoint radius:fromRadius startAngle:0 endAngle:2 * M_PI clockwise:YES].CGPath;
_maskLayer.opacity = 1;
_maskLayer.position = centerPoint;
_photoImage = [[UIImageView alloc] init];
_photoImage.contentMode = UIViewContentModeScaleAspectFill;
_photoImage.frame = self.view.frame;
_photoImage.image = newImage;
_photoImage.layer.mask = _maskLayer;
[self.view addSubview:_photoImage];
}
- (LOTAnimationView *)animationView {
if (!_animationView) {
_animationView = [LOTAnimationView animationNamed:@"gene_loading"];
_animationView.contentMode = UIViewContentModeScaleAspectFit;
_animationView.loopAnimation = YES;
_animationView.frame = CGRectMake(0, 85, 177, 60);
_animationView.centerX = centerPoint.x;
[self.view addSubview:_animationView];
}
return _animationView;
}
- (void)removeAnimation {
[_maskLayer removeAllAnimations];
[_animationView stop];
}
- (void)startShowPhotoAnimation {
CGPathRef fromPath = _maskLayer.path;
CGPathRef toPath = [UIBezierPath bezierPathWithArcCenter:centerPoint radius:toRadius startAngle:0 endAngle:2 * M_PI clockwise:YES].CGPath;
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
pathAnimation.fromValue = (__bridge id _Nullable)(fromPath);
pathAnimation.toValue = (__bridge id _Nullable)(toPath);
pathAnimation.duration = showAnimaitionTime;
_maskLayer.path = toPath;
[_maskLayer addAnimation:pathAnimation forKey:@"path"];
[self performSelector:@selector(startCircleAnimation) withObject:nil afterDelay:showAnimaitionTime];
}
- (void)startCircleAnimation {
// 开始头部动画
[self.animationView playFromProgress:0 toProgress:1 withCompletion:^(BOOL animationFinished) {}];
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
pathAnimation.calculationMode = kCAAnimationCubicPaced;
pathAnimation.fillMode = kCAFillModeForwards;
pathAnimation.removedOnCompletion = NO;
pathAnimation.duration = circleAnimaitionTime;
pathAnimation.repeatCount = OPEN_MAX;
CGMutablePathRef curvedPath = CGPathCreateMutable();
//center为圆心,circleRadius为半径 (startAngle,endAngle)为起始角度和结束角度,0为顺时针,1 为逆时针
CGPathAddArc(curvedPath, NULL, centerPoint.x + circleRadius / 2, centerPoint.y, circleRadius / 2, M_PI, 2 * M_PI, 0);
CGPathAddArc(curvedPath, NULL, centerPoint.x, centerPoint.y, circleRadius, 0, M_PI, 0);
CGPathAddArc(curvedPath, NULL, centerPoint.x - circleRadius / 2, centerPoint.y, circleRadius / 2, M_PI, 2 * M_PI, 0);
pathAnimation.path = curvedPath;
CGPathRelease(curvedPath);
[_maskLayer addAnimation:pathAnimation forKey:@"position"];
}
- (void)startHidePhotoAnimation {
// 删除动画
[self.animationView stop];
[self.animationView removeFromSuperview];
CGPathRef fromPath = _maskLayer.path;
CGPathRef toPath = [UIBezierPath bezierPathWithArcCenter:centerPoint radius:fromRadius * 2 startAngle:0 endAngle:2 * M_PI clockwise:YES].CGPath;
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
pathAnimation.fromValue = (__bridge id _Nullable)(fromPath);
pathAnimation.toValue = (__bridge id _Nullable)(toPath);
pathAnimation.duration = hideAnimaitionTime;
_maskLayer.path = toPath;
[_maskLayer addAnimation:pathAnimation forKey:@"path"];
}
- (void)pushToNextVC:(id)objcet {
GMFaceV3Model *faceModel = (GMFaceV3Model *)objcet;
GMFacePointViewController *pointVc = [[GMFacePointViewController alloc] initWithModel:faceModel];
pointVc.isGene = YES;
[self.navigationController pushViewController:pointVc animated:YES];
}
@end
//
// GMGeneMemoryResultViewController.h
// Gengmei
//
// Created by 朱璇 on 2019/12/6.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMBaseWebViewController.h"
NS_ASSUME_NONNULL_BEGIN
@interface GMGeneMemoryResultViewController : GMBaseWebViewController
@property(nonatomic, copy)NSString *webPath; // 需要加载的URL
@end
NS_ASSUME_NONNULL_END
//
// GMGeneMemoryResultViewController.m
// Gengmei
//
// Created by 朱璇 on 2019/12/6.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMGeneMemoryResultViewController.h"
#import "ALShareSheet.h"
@interface GMGeneMemoryResultViewController ()<WKWebViewDelegate, GMClientH5BridgeDelegate>
@property (nonatomic, strong) NSDictionary *dataDict;
@property (nonatomic, strong) UIImage *shareImage; // 前端传递过来的图片
@property (nonatomic, strong) GMImageView *shareImageView; // 分享展示的图片
@property (nonatomic, strong) ALShareSheet *shareSheet; // 分享弹出框
@property (nonatomic, copy) NSString *imUrl;
@end
@implementation GMGeneMemoryResultViewController
- (void)initController {
[super initController];
// self.pageName = @"face_institute_report";
self.navigationBar.backgroundColor = UIColor.clearColor;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self preferredStatusBarStyle];
[UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent;
[UIApplication sharedApplication].statusBarHidden = YES;
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleDefault;
[UIApplication sharedApplication].statusBarHidden = NO;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.controlNavigationByYou = YES;
self.navigationBar.hidden = YES;
self.fd_interactivePopDisabled = YES;
self.webCompent.delegate = self;
self.webCompent.clientH5Object.delegate = self;
[self.webCompent mas_updateConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(0);
}];
if (_webPath.length > 0) {
self.webCompent.fullUrl = [NSString stringWithFormat:@"%@%@",GMServerDomains.apiHost, _webPath];
} else {
self.webCompent.fullUrl = [NSString stringWithFormat:@"%@%@",GMServerDomains.apiHost, API_AI_RESEARCH_REPORT];
}
[self.webCompent webviewLoad:self];
}
- (void)setWebPath:(NSString *)webPath {
_webPath = webPath;
}
#pragma mark - 前端与移动端连调方法
- (void)jsGlobalDataLoaded:(NSString *)jsonString {
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
_jsGlobalObject = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:nil];
if (_jsGlobalObject == nil) {
return;
}
if (_jsGlobalObject[@"page_name"]) {
self.pageName = _jsGlobalObject[@"page_name"];
}
// 调取前端方法
NSDictionary *responseDict = [GMCache fetchObjectAtDocumentPathWithkey:kGMGeneMemoryLastData];
NSString *dictStr = [NSString convertToPrettyPrintedJsonString: responseDict];
NSNumber *gender = [GMCache fetchObjectAtDocumentPathWithkey:kGMGeneMemoryGender];
NSString *javaScriptStr = [NSString stringWithFormat:@"window.gm.pack.run('jsPageRenderData',%@, %d)", dictStr, gender.intValue];
[self.webCompent.webView evaluateJavaScript:javaScriptStr completionHandler:nil];
}
-(void)jsControlBackToMethod:(NSString *)str {
if([str isEqualToString:@"pop"]){ // 返回到拍照测试页面
[self backFurther];
} else if ([str isEqualToString:@"face"]) { // 返回测脸页面
[[NSNotificationCenter defaultCenter] postNotificationName:@"GMTestSkinAnimationWebViewSenBack" object:nil];
[self backFurther];
} else if ([str isEqualToString:@"reserch"]) { // 返回面孔起源
[self backFurther];
} else if ([str isEqualToString:@"skin"]) { // 测肤
[[NSNotificationCenter defaultCenter] postNotificationName:@"GMFaceInfoSharePopToFaceSkin" object:nil];
[self backFurther];
} else if ([str isEqualToString:@"back"]) { // 返回上一页
[self.navigationController popViewControllerAnimated:YES];
}
}
- (void)jsJumpToResearchPage:(NSString *)url {
GMGeneMemoryResultViewController *geneListVC = [[GMGeneMemoryResultViewController alloc] init];
geneListVC.webPath = url;
[self.navigationController pushViewController:geneListVC animated:YES];
}
- (NSString *)jsGetLocalStorage:(NSString *)key {
NSString *cacheKey = [GMCache fetchObjectAtDocumentPathWithkey:key];
NSString *javaScriptStr = [NSString stringWithFormat:@"window.gm.pack.run('getLocalStorage','%@')",cacheKey];
[self.webCompent.webView evaluateJavaScript:javaScriptStr completionHandler:nil];
if (![cacheKey isNonEmpty]) {
return @"";
}
return cacheKey;
}
// 当图片生成时,前端会调用这个方法
- (void)shareResearchInfoImageMethod:(NSString *)imageDataStr{
_shareImage = [self stringToImageWith:imageDataStr imageStr:@"imageString"];
if (!_shareImage) {
[self toast:@"糟糕,生成失败了,再试一次吧"];
return;
}
// 展示分享框
[self shareFaceInfo];
// 保存图片到相册
UIImageWriteToSavedPhotosAlbum(_shareImage, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);
[self toast:@"图片已存至相册,快分享给朋友炫耀一下"];
}
// 解析前端传递过来的图片
- (UIImage *)stringToImageWith:(NSString *)imageData imageStr:(NSString *)imageStr {
NSData *jsonData = [imageData dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:nil];
NSString *imageString = dict[imageStr] ?: @"";
UIImage *image = [UIImage imageWithData:[[NSData alloc] initWithBase64EncodedString:imageString options:NSDataBase64DecodingIgnoreUnknownCharacters]];
return image;
}
- (void)shareFaceInfo {
CGFloat scale1 = _shareImage.size.height / _shareImage.size.width;
CGFloat width1 = ShareImageHeight / scale1;
self.shareImageView.image = self.shareImage;
self.shareImageView.frame = CGRectMake((MAINSCREEN_WIDTH - width1)/2, OCNavigationBar.barHeight + 10, width1, ShareImageHeight);
[self.shareSheet addSubview:self.shareImageView];
[self.shareSheet show];
[UIView animateWithDuration:0.5 animations:^{
_shareImageView.alpha = 1.0;
}];
}
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo {}
// 返回按钮点击事件
-(void)backFurther {
BOOL isPop = YES;
for (UIViewController *vc in AppDelegate.navigation.childViewControllers) {
if ([vc isKindOfClass:[NewFaceShotController class]]){
isPop = NO;
[AppDelegate.navigation popToViewController:vc animated:YES];
return;
}
}
if (isPop) {
[AppDelegate.navigation popToRootViewControllerAnimated:YES];
}
}
- (UIStatusBarStyle)preferredStatusBarStyle {
return UIStatusBarStyleLightContent;
}
#pragma mark - 懒加载数据
- (ALShareSheet *)shareSheet {
if (!_shareSheet) {
_shareSheet = [[ALShareSheet alloc] init];
_shareSheet.bgAlphaColor = RGBCOLOR_HEX(0xF4F3F8);
__weak typeof(self) weakSelf = self;
_shareSheet.hiddenBlock = ^{ // 隐藏
[UIView animateWithDuration:0.25 animations:^{
weakSelf.shareImageView.alpha = 0.0;
} completion:^(BOOL finished) {
[weakSelf.shareImageView removeFromSuperview];
}];
};
_shareSheet.shareTypeBlock = ^(GMSharePlatform platform) { // 分享
[[GMShareSDK shareInstance] shareImage:weakSelf.shareImage platform:platform];
[Phobos track:@"page_click_share_channel" attributes:@{@"page_name":SafeString(weakSelf.pageName),
@"share_channel":SafeString([GMShareSDK shareChannel:platform])}];
};
}
return _shareSheet;
}
-(GMImageView *)shareImageView {
if (!_shareImageView) {
_shareImageView = [[GMImageView alloc] init];
_shareImageView.frame = CGRectMake(0, 0, MAINSCREEN_WIDTH, MAINSCREEN_HEIGHT);
_shareImageView.contentMode = UIViewContentModeScaleAspectFill;
}
return _shareImageView;
}
@end
{"v":"5.5.8","fr":60,"ip":608,"op":779,"w":750,"h":1334,"nm":"字+进度","ddd":0,"assets":[{"id":"image_0","w":476,"h":63,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAdwAAAA/CAYAAABKBHltAAADzUlEQVR4Xu3c0VHbQBCA4VUHlEAJlJAS0gF0EDpIqCB0AB1QAqUtc4rw4DG2Jdnah9ynh0we0N3cl535x84NQ6x4MvNvRDyueNUrBAgQIECgS4Fhyakz8yYiXiLi55L3/CwBAgQIEOhdYHZwM/M2It4i4q53NOcnQIAAAQJLBWYFNzNbZFtsW3Q9BAgQIECAwEKBs8HNzPb1cfsauX2d7CFAgAABAgRWCJwMbmb+iojnFet6hQABAgQIEPgicDS4biKbEwIECBAgcD2Bg+C6iXw9XCsRIECAAIFPgb3guolsMAgQIECAwDYCu+C6ibwNsFUJECBAgEATGIPrJrJhIECAAAEC2woMbiJvC2x1AgQIECAwfsLNzERBgAABAgQIbCsguNv6Wp0AAQIECIwCgmsQCBAgQIBAgYDgFiDbggABAgQICK4ZIECAAAECBQKCW4BsCwIECBAgILhmgAABAgQIFAgIbgGyLQgQIECAgOCaAQIECBAgUCAguAXItiBAgAABAoJrBggQIECAQIGA4BYg24IAAQIECAiuGSBAgAABAgUCgluAbAsCBAgQICC4ZoAAAQIECBQICG4Bsi0IECBAgIDgmgECBAgQIFAgILgFyLYgQIAAAQKCawYIECBAgECBgOAWINuCAAECBAgIrhkgQIAAAQIFAoJbgGwLAgQIECAguGaAAAECBAgUCAhuAbItCBAgQICA4JoBAgQIECBQICC4Bci2IECAAAECgmsGCBAgQIBAgYDgFiDbggABAgQICK4ZIECAAAECBQKCW4BsCwIECBAgILhmgAABAgQIFAgIbgGyLQgQIECAgOCaAQIECBAgUCAguAXItiBAgAABAoJrBggQIECAQIGA4BYg24IAAQIEuhd4EtzuZwAAAQIECGws8DQMwx/B3VjZ8gQIECDQtcAY2ybQgtv+8rtrDocnQIAAAQLXF9jFdgxu+0N0r69sRQIECBDoWmAvtrvgTtF9iIiXrnkcngABAgQIXC5wENu94E7R/RERbxFxc/l+ViBAgAABAt0JfBvbg+BO0b2bonvbHZMDEyBAgACB9QJHY/ttcKfotti+R4Torof3JgECBAj0I3AytkeDK7r9TIiTEiBAgMDFAmdjezK4onvxP4AFCBAgQOD/F5gV27PB/RLddpGq/d+uhwABAgQIEPgnMDu2s4I7RbfdWm7RbbeYPQQIECBAoHeBRbGdHdxP1cx8jYj73pWdnwABAgS6Flgc28XBnT7t+lWQXc+ZwxMgQKBrgVWxXRVc0e160ByeAAECPQusjm1D+wCn4QsQvi1RFgAAAABJRU5ErkJggg==","e":1},{"id":"image_1","w":528,"h":135,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_2","w":780,"h":63,"u":"","p":"data:image/png;base64,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","e":1},{"id":"comp_0","layers":[{"ddd":0,"ind":1,"ty":2,"nm":"图片2.png","cl":"png","refId":"image_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[375,859,0],"ix":2},"a":{"a":0,"k":[238,31.5,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"a","pt":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":629,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[0,0],[0,63],[-4,63],[-4,0]],"c":true}],"e":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[0,0],[0,63],[476,63],[476,0]],"c":true}]},{"t":739}],"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"f":{"a":0,"k":[0,0],"ix":2},"nm":"蒙版 1"}],"ip":0,"op":1200,"st":0,"cp":false,"bm":0}]}],"fonts":{"list":[{"fName":"Avenir-Heavy","fFamily":"Avenir","fStyle":"Heavy","ascent":75.5996704101562}]},"layers":[{"ddd":0,"ind":1,"ty":5,"nm":"100","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":734,"s":[0],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":744,"s":[100],"e":[100]},{"t":760}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[547,759,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"t":{"d":{"k":[{"s":{"s":62,"f":"Avenir-Heavy","t":"100","j":1,"tr":95,"lh":74.4,"ls":0,"fc":[1,1,1]},"t":0}]},"p":{},"m":{"g":1,"a":{"a":0,"k":[0,0],"ix":2}},"a":[]},"ip":125,"op":1325,"st":125,"cp":true,"bm":0},{"ddd":0,"ind":2,"ty":5,"nm":"75","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":702,"s":[0],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":712,"s":[100],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":724,"s":[100],"e":[0]},{"t":734}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[547,759,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"t":{"d":{"k":[{"s":{"s":62,"f":"Avenir-Heavy","t":"75","j":1,"tr":95,"lh":74.4,"ls":0,"fc":[1,1,1]},"t":0}]},"p":{},"m":{"g":1,"a":{"a":0,"k":[0,0],"ix":2}},"a":[]},"ip":93,"op":1293,"st":93,"cp":true,"bm":0},{"ddd":0,"ind":3,"ty":5,"nm":"50","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":671,"s":[0],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":681,"s":[100],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":692,"s":[100],"e":[0]},{"t":702}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[547,759,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"t":{"d":{"k":[{"s":{"s":62,"f":"Avenir-Heavy","t":"50","j":1,"tr":95,"lh":74.4,"ls":0,"fc":[1,1,1]},"t":0}]},"p":{},"m":{"g":1,"a":{"a":0,"k":[0,0],"ix":2}},"a":[]},"ip":65,"op":1265,"st":65,"cp":true,"bm":0},{"ddd":0,"ind":4,"ty":5,"nm":"25","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":643,"s":[0],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":653,"s":[100],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":661,"s":[100],"e":[0]},{"t":671}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[543,759,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"t":{"d":{"k":[{"s":{"s":62,"f":"Avenir-Heavy","t":"25","j":1,"tr":95,"lh":74.4,"ls":0,"fc":[1,1,1]},"t":0}]},"p":{},"m":{"g":1,"a":{"a":0,"k":[0,0],"ix":2}},"a":[]},"ip":34,"op":1234,"st":34,"cp":true,"bm":0},{"ddd":0,"ind":5,"ty":5,"nm":"0","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":609,"s":[0],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":629,"s":[100],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":633,"s":[100],"e":[0]},{"t":643}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[547,759,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"t":{"d":{"k":[{"s":{"s":62,"f":"Avenir-Heavy","t":"0","j":1,"tr":95,"lh":74.4,"ls":0,"fc":[1,1,1]},"t":0}]},"p":{},"m":{"g":1,"a":{"a":0,"k":[0,0],"ix":2}},"a":[]},"ip":0,"op":1200,"st":0,"cp":true,"bm":0},{"ddd":0,"ind":6,"ty":5,"nm":"%","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":609,"s":[0],"e":[100]},{"t":629}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[593.25,753.111,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"t":{"d":{"k":[{"s":{"s":39,"f":"Avenir-Heavy","t":"%","j":1,"tr":95,"lh":46.8,"ls":0,"fc":[1,1,1]},"t":0}]},"p":{},"m":{"g":1,"a":{"a":0,"k":[0,0],"ix":2}},"a":[]},"ip":0,"op":1200,"st":0,"cp":true,"bm":0},{"ddd":0,"ind":7,"ty":0,"nm":"进度","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[375,667,0],"ix":2},"a":{"a":0,"k":[375,667,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"w":750,"h":1334,"ip":0,"op":1200,"st":0,"cp":false,"bm":0},{"ddd":0,"ind":8,"ty":2,"nm":"图片.png","cl":"png","refId":"image_1","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":609,"s":[0],"e":[100]},{"t":629}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[375,847,0],"ix":2},"a":{"a":0,"k":[264,67.5,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":1200,"st":0,"cp":false,"bm":0},{"ddd":0,"ind":9,"ty":2,"nm":"中华面孔报告生正在成中_1.png","cl":"png","refId":"image_2","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":609,"s":[0],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":624,"s":[100],"e":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":639,"s":[0],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":654,"s":[100],"e":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":669,"s":[0],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":684,"s":[100],"e":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":699,"s":[0],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":714,"s":[100],"e":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":729,"s":[0],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":744,"s":[100],"e":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":759,"s":[0],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":774,"s":[100],"e":[0]},{"t":789}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[375,667,0],"ix":2},"a":{"a":0,"k":[390,31.5,0],"ix":1},"s":{"a":0,"k":[67,67,100],"ix":6}},"ao":0,"ip":0,"op":1200,"st":0,"cp":false,"bm":0}],"markers":[],"chars":[{"ch":"%","size":39,"style":"Heavy","w":90.7,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.967,-2.233],[-1.7,-1.7],[-2.234,-0.966],[-2.6,0],[-2.234,0.966],[-1.7,1.7],[-0.967,2.234],[0,2.6],[0.966,2.234],[1.7,1.7],[2.233,0.967],[2.6,0],[2.233,-0.966],[1.7,-1.7],[0.966,-2.233],[0,-2.6]],"o":[[0.966,2.234],[1.7,1.7],[2.233,0.966],[2.6,0],[2.233,-0.966],[1.7,-1.7],[0.966,-2.233],[0,-2.6],[-0.967,-2.233],[-1.7,-1.7],[-2.234,-0.966],[-2.6,0],[-2.234,0.967],[-1.7,1.7],[-0.967,2.234],[0,2.6]],"v":[[51.95,-10.15],[55.95,-4.25],[61.85,-0.25],[69.1,1.2],[76.35,-0.25],[82.25,-4.25],[86.25,-10.15],[87.7,-17.4],[86.25,-24.65],[82.25,-30.55],[76.35,-34.55],[69.1,-36],[61.85,-34.55],[55.95,-30.55],[51.95,-24.65],[50.5,-17.4]],"c":true},"ix":2},"nm":"%","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[1.733,-1.733],[2.533,0],[1.733,1.734],[0,2.534],[-1.734,1.734],[-2.534,0],[-1.734,-1.733],[0,-2.533]],"o":[[-1.734,1.734],[-2.534,0],[-1.734,-1.733],[0,-2.533],[1.733,-1.733],[2.533,0],[1.733,1.734],[0,2.534]],"v":[[75.5,-11],[69.1,-8.4],[62.7,-11],[60.1,-17.4],[62.7,-23.8],[69.1,-26.4],[75.5,-23.8],[78.1,-17.4]],"c":true},"ix":2},"nm":"%","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[-0.967,-2.233],[-1.7,-1.7],[-2.234,-0.966],[-2.6,0],[-2.234,0.967],[-1.7,1.7],[-0.967,2.234],[0,2.6],[0.966,2.234],[1.7,1.7],[2.233,0.967],[2.6,0],[2.233,-0.966],[1.7,-1.7],[0.966,-2.233],[0,-2.6]],"o":[[0.966,2.234],[1.7,1.7],[2.233,0.967],[2.6,0],[2.233,-0.966],[1.7,-1.7],[0.966,-2.233],[0,-2.6],[-0.967,-2.233],[-1.7,-1.7],[-2.234,-0.966],[-2.6,0],[-2.234,0.967],[-1.7,1.7],[-0.967,2.234],[0,2.6]],"v":[[4.45,-46.15],[8.45,-40.25],[14.35,-36.25],[21.6,-34.8],[28.85,-36.25],[34.75,-40.25],[38.75,-46.15],[40.2,-53.4],[38.75,-60.65],[34.75,-66.55],[28.85,-70.55],[21.6,-72],[14.35,-70.55],[8.45,-66.55],[4.45,-60.65],[3,-53.4]],"c":true},"ix":2},"nm":"%","mn":"ADBE Vector Shape - Group","hd":false},{"ind":3,"ty":"sh","ix":4,"ks":{"a":0,"k":{"i":[[1.733,-1.733],[2.533,0],[1.733,1.734],[0,2.534],[-1.734,1.734],[-2.534,0],[-1.734,-1.733],[0,-2.533]],"o":[[-1.734,1.734],[-2.534,0],[-1.734,-1.733],[0,-2.533],[1.733,-1.733],[2.533,0],[1.733,1.734],[0,2.534]],"v":[[28,-47],[21.6,-44.4],[15.2,-47],[12.6,-53.4],[15.2,-59.8],[21.6,-62.4],[28,-59.8],[30.6,-53.4]],"c":true},"ix":2},"nm":"%","mn":"ADBE Vector Shape - Group","hd":false},{"ind":4,"ty":"sh","ix":5,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[20.5,-0.6],[28.8,3.2],[70.4,-70.2],[62.1,-74]],"c":true},"ix":2},"nm":"%","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"%","np":8,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Avenir"},{"ch":"0","size":62,"style":"Heavy","w":59.2,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.9,-3.966],[-1.467,-2.7],[-1.834,-1.633],[-1.9,-0.833],[-1.767,-0.267],[-1.334,0],[-1.767,0.266],[-1.9,0.834],[-1.834,1.634],[-1.467,2.7],[-0.9,3.967],[0,5.6],[0.9,3.967],[1.466,2.7],[1.833,1.634],[1.9,0.834],[1.766,0.267],[1.333,0],[1.766,-0.266],[1.9,-0.833],[1.833,-1.633],[1.466,-2.7],[0.9,-3.966],[0,-5.6]],"o":[[0.9,3.967],[1.466,2.7],[1.833,1.634],[1.9,0.834],[1.766,0.266],[1.333,0],[1.766,-0.267],[1.9,-0.833],[1.833,-1.633],[1.466,-2.7],[0.9,-3.966],[0,-5.6],[-0.9,-3.966],[-1.467,-2.7],[-1.834,-1.633],[-1.9,-0.833],[-1.767,-0.266],[-1.334,0],[-1.767,0.267],[-1.9,0.834],[-1.834,1.634],[-1.467,2.7],[-0.9,3.967],[0,5.6]],"v":[[5.35,-21.05],[8.9,-11.05],[13.85,-4.55],[19.45,-0.85],[24.95,0.8],[29.6,1.2],[34.25,0.8],[39.75,-0.85],[45.35,-4.55],[50.3,-11.05],[53.85,-21.05],[55.2,-35.4],[53.85,-49.75],[50.3,-59.75],[45.35,-66.25],[39.75,-69.95],[34.25,-71.6],[29.6,-72],[24.95,-71.6],[19.45,-69.95],[13.85,-66.25],[8.9,-59.75],[5.35,-49.75],[4,-35.4]],"c":true},"ix":2},"nm":"0","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[-0.3,2.9],[-0.934,2.6],[-1.8,1.8],[-3,0],[-1.8,-1.8],[-0.934,-2.6],[-0.3,-2.9],[0,-2.2],[0.3,-2.9],[0.933,-2.6],[1.8,-1.8],[3,0],[1.8,1.8],[0.933,2.6],[0.3,2.9],[0,2.2]],"o":[[0.3,-2.9],[0.933,-2.6],[1.8,-1.8],[3,0],[1.8,1.8],[0.933,2.6],[0.3,2.9],[0,2.2],[-0.3,2.9],[-0.934,2.6],[-1.8,1.8],[-3,0],[-1.8,-1.8],[-0.934,-2.6],[-0.3,-2.9],[0,-2.2]],"v":[[16.45,-43.05],[18.3,-51.3],[22.4,-57.9],[29.6,-60.6],[36.8,-57.9],[40.9,-51.3],[42.75,-43.05],[43.2,-35.4],[42.75,-27.75],[40.9,-19.5],[36.8,-12.9],[29.6,-10.2],[22.4,-12.9],[18.3,-19.5],[16.45,-27.75],[16,-35.4]],"c":true},"ix":2},"nm":"0","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"0","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Avenir"},{"ch":"2","size":62,"style":"Heavy","w":59.2,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[-2.034,3.234],[0,4.467],[1.2,2.534],[2.066,1.634],[2.766,0.834],[3.2,0],[2.733,-0.866],[2.066,-1.666],[1.3,-2.5],[0.266,-3.266],[0,0],[-1.934,1.734],[-3,0],[-1.234,-0.433],[-0.934,-0.8],[-0.567,-1.133],[0,-1.466],[0.466,-1.1],[0.7,-1],[0.866,-0.9],[0.8,-0.8],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[3.2,-2.933],[2.033,-3.233],[0,-3.4],[-1.2,-2.533],[-2.067,-1.633],[-2.767,-0.833],[-3.2,0],[-2.734,0.867],[-2.067,1.667],[-1.3,2.5],[0,0],[0.4,-2.933],[1.933,-1.733],[1.4,0],[1.233,0.434],[0.933,0.8],[0.566,1.134],[0,1.2],[-0.467,1.1],[-0.7,1],[-0.867,0.9],[0,0],[0,0]],"v":[[5.9,0],[53.3,0],[53.3,-10.8],[20.5,-10.8],[42.4,-31.1],[50.25,-40.35],[53.3,-51.9],[51.5,-60.8],[46.6,-67.05],[39.35,-70.75],[30.4,-72],[21.5,-70.7],[14.3,-66.9],[9.25,-60.65],[6.9,-52],[19.5,-51],[23,-58],[30.4,-60.6],[34.35,-59.95],[37.6,-58.1],[39.85,-55.2],[40.7,-51.3],[40,-47.85],[38.25,-44.7],[35.9,-41.85],[33.4,-39.3],[5.9,-12.7]],"c":true},"ix":2},"nm":"2","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"2","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Avenir"},{"ch":"5","size":62,"style":"Heavy","w":59.2,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[-2.567,0.6],[-2.6,0],[-2,-0.5],[-1.534,-1.1],[-0.9,-1.733],[0,-2.466],[0.6,-1.466],[1.1,-1.1],[1.5,-0.6],[1.8,0],[1.933,1.467],[0.8,2.534],[0,0],[-3.967,-2.666],[-5.667,0],[-3.134,1.133],[-2.3,2.1],[-1.3,3.034],[0,3.8],[1.166,2.867],[2.066,2.067],[2.9,1.1],[3.466,0],[1.433,-0.133],[0.933,-0.333],[0,0],[0,0]],"o":[[0,0],[0,0],[2.133,-1.066],[2.566,-0.6],[2.2,0],[2,0.5],[1.533,1.1],[0.9,1.734],[0,1.734],[-0.6,1.467],[-1.1,1.1],[-1.5,0.6],[-3.067,0],[-1.934,-1.466],[0,0],[1.666,5.467],[3.966,2.667],[3.6,0],[3.133,-1.133],[2.3,-2.1],[1.3,-3.033],[0,-3.6],[-1.167,-2.866],[-2.067,-2.066],[-2.9,-1.1],[-1.267,0],[-1.434,0.134],[0,0],[0,0],[0,0]],"v":[[50.5,-70.8],[10.2,-70.8],[9.5,-32.6],[16.55,-35.1],[24.3,-36],[30.6,-35.25],[35.9,-32.85],[39.55,-28.6],[40.9,-22.3],[40,-17.5],[37.45,-13.65],[33.55,-11.1],[28.6,-10.2],[21.1,-12.4],[17,-18.4],[4.4,-15],[12.85,-2.8],[27.3,1.2],[37.4,-0.5],[45.55,-5.35],[50.95,-13.05],[52.9,-23.3],[51.15,-33],[46.3,-40.4],[38.85,-45.15],[29.3,-46.8],[25.25,-46.6],[21.7,-45.9],[22,-60],[50.5,-60]],"c":true},"ix":2},"nm":"5","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"5","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Avenir"},{"ch":"7","size":62,"style":"Heavy","w":59.2,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[4.2,-59.4],[38.8,-59.4],[9.9,0],[24,0],[52.3,-59.8],[52.3,-70.8],[4.2,-70.8]],"c":true},"ix":2},"nm":"7","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"7","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Avenir"},{"ch":"1","size":62,"style":"Heavy","w":59.2,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[26.8,0],[38.8,0],[38.8,-70.8],[27.9,-70.8],[6.5,-53],[13.5,-44.7],[26.8,-57]],"c":true},"ix":2},"nm":"1","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Avenir"}]}
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
{"v":"5.5.9","fr":60,"ip":70,"op":241,"w":367,"h":131,"nm":"button","ddd":0,"assets":[{"id":"image_0","w":547,"h":196,"u":"","p":"data:image/png;base64,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","e":1}],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"形状图层 3","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":116,"s":[0],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":126,"s":[100],"e":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":136,"s":[0],"e":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[0],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":176,"s":[100],"e":[0]},{"t":186}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[300.711,64,0],"ix":2},"a":{"a":0,"k":[81.211,-428.783,0],"ix":1},"s":{"a":0,"k":[60,45,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[12.422,16.434],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"矩形路径 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":true},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[81.211,-428.783],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"矩形 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"rd","nm":"圆角 1","r":{"a":0,"k":10,"ix":1},"ix":2,"mn":"ADBE Vector Filter - RC","hd":false}],"ip":94,"op":185,"st":0,"cp":true,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"形状图层 2","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":106,"s":[0],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":116,"s":[100],"e":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":136,"s":[0],"e":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":156,"s":[0],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[100],"e":[0]},{"t":186}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[282.711,64,0],"ix":2},"a":{"a":0,"k":[81.211,-428.783,0],"ix":1},"s":{"a":0,"k":[60,45,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[12.422,16.434],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"矩形路径 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":true},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[81.211,-428.783],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"矩形 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"rd","nm":"圆角 1","r":{"a":0,"k":10,"ix":1},"ix":2,"mn":"ADBE Vector Filter - RC","hd":false}],"ip":94,"op":185,"st":0,"cp":true,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"形状图层 1","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":96,"s":[0],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":106,"s":[100],"e":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":136,"s":[0],"e":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":146,"s":[0],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":156,"s":[100],"e":[0]},{"t":186}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[264.211,64.217,0],"ix":2},"a":{"a":0,"k":[81.211,-428.783,0],"ix":1},"s":{"a":0,"k":[60,45,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[12.422,16.434],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"矩形路径 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":true},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[81.211,-428.783],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"矩形 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"rd","nm":"圆角 1","r":{"a":0,"k":10,"ix":1},"ix":2,"mn":"ADBE Vector Filter - RC","hd":false}],"ip":94,"op":185,"st":0,"cp":true,"bm":0},{"ddd":0,"ind":4,"ty":2,"nm":"BUTTON_2.png","cl":"png","refId":"image_0","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":74,"s":[0],"e":[100]},{"t":94}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[183,65,0],"ix":2},"a":{"a":0,"k":[273.5,98,0],"ix":1},"s":{"a":0,"k":[67,67,100],"ix":6}},"ao":0,"ip":73,"op":255,"st":-6,"cp":false,"bm":0}],"markers":[]}
\ No newline at end of file
//
// NewFaceShotController.swift
// Gengmei
//
// Created by 刘鹿杰 on 2019/6/19.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
import UIKit
import GMBaseSwift
import GMAlbum
import GMBase
import SnapKit
import GMKit
import GMUtil
import AVFoundation
public var didSetHuman = false
public var didSetNoHumam = false
@objcMembers
class NewFaceShotController: GMBaseController, GMFaceControllerNavigation, GMPhotoPickDismissDelegate, FaceGuideViewDelegate, GMFaceBottomViewDelegate, GMSkinGuidancePopViewDelegate, GMGeneMemoryGenderSelectViewDelegate, CAAnimationDelegate, AVCaptureVideoDataOutputSampleBufferDelegate, GMVoiceSwitchViewDelegate {
// 创建照相机必须使用的类
var device: AVCaptureDevice!
var input: AVCaptureDeviceInput! //AVCaptureDeviceInput 代表输入设备,他使用AVCaptureDevice 来初始化
var output: AVCaptureVideoDataOutput! //当启动摄像头开始捕获输入
var imageOutPut: AVCaptureStillImageOutput? = AVCaptureStillImageOutput()
var session: AVCaptureSession = AVCaptureSession()
var previewLayer: AVCaptureVideoPreviewLayer!
// 人脸识别必须使用的类
var faceDetector: CIDetector?
var videoDataOutputQueue: DispatchQueue?
// 测肤使用到的属性
var rightFace = 0
var farFace = 0
var nearFace = 0
var closeEyeTimes = 0
var isnoCheckFace = true
var isDelayShot = true
var getFace = 0
var beginToTakePicture = false
var didTakePicture = false
var timeString: String? = nil
var exif: AnyObject? = nil
var imgWidth: CGFloat = 0
var options = [String: AnyObject]()
let faceStatePromter = FaceStatePromter()
let notificationCenter : NotificationCenter = NotificationCenter.default
// 面孔起源
var geneMemoryGenderSelectView: GMGeneMemoryGenderSelectView!
// 测肤View封装
fileprivate var skinAIView: GMFaceSkinView!
var skinFrontAIView: GMFaceSkinFrontView!
var voiceView: GMVoiceSwitchView!
fileprivate var bottomView: GMFaceBottomView!
// 默认选中相机记录
var cameraType: GMScanAnimationType = .scanFace
var cameraDirection: CameraDeviceDirection = .isCameraFront
// 灰度判断
var skinGray = GMLaunchManager.share()?.appConfigObject.skinCameraGray
// 是否有签到任务
var voiceNum: Float!
var haveTask: Bool = false
// 记录是否距离合适
var distanceRight: Bool = false
// 校验是否已经埋点
var isScanTrack: Bool = false
var isTestTrack: Bool = false
var isGeneTrack: Bool = false
// 判断测肤远端上次报告是否存在
let haveSkinLastReport = GMLaunchManager.share()?.appConfigObject.haveScanSkin
var selectedGender = 0 // 面孔起源 选择的性别
// 摄像头方向
enum CameraDeviceDirection {
case isCameraFront // 前置照摄像头
case isCameraBack // 后置照摄像头
}
// MARK: - 控制器的生命周期
override func initController() {
super.initController()
commonSetupNavigation()
pageName = "face_scan"
navigationBar.leftIcon = "NewFaceBack"
self.fd_interactivePopDisabled = true
if GMLoginManager.shareInstance().user.userId > 0 {
businessId = String(GMLoginManager.shareInstance().user.userId)
} else {
businessId = ""
}
guard let extraP = AppDelegate.shareInstance().extraParam else {
return
}
extraParam = extraP
AppDelegate.shareInstance().extraParam = ""
}
override func viewDidLoad() {
super.viewDidLoad()
setUpFaceDetector() // 设置人脸识别特别及音量
setUpCamera() // 设置照相机
addSkinAIViewFrontUI() // 添加测肤时前置摄像头时的UI
addSkinAIViewBackUI() // 添加测肤时后置摄像头时的UI
addGeneMemoryGenderView() // 添加选择性别view
addVoiceView() // 添加voiceView
addBottomView() // 添加bottomView
addNotification() // 添加通知
fetchLastReport() // 上次报告查询
guard var extraP = stringValueDic(extraParam) else { return}
extraP["land_tab_name"] = self.getTabName()
extraParam = NSString.convert(toBriefJsonString: extraP)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.session.startRunning()
beginToTakePicture = false
didTakePicture = false
isDelayShot = true
setOutputVolume() // 设置音量
bottomView.selectedType = cameraType
if cameraType == .scanSkin { // AI测肤
voiceView.isHidden = false
geneMemoryGenderSelectView.isHidden = true
isnoCheckFace = false
skinFrontAIView.isHidden = cameraDirection == .isCameraFront ? false : true
skinAIView.isHidden = cameraDirection == .isCameraBack ? false : true
let skinLastReport = GMCache.fetchObject(atDocumentPathWithkey: "GMTestSkinIsShowData")
let showSkinLastReport: Bool = (skinLastReport != nil || (haveSkinLastReport ?? false))
bottomView.lastReportButton.isHidden = !showSkinLastReport
isSkinShowNewGuide() // 添加首次展示提示框
} else if cameraType == .scanFace { // AI侧脸
geneMemoryGenderSelectView.isHidden = true
let faceId = GMCache.fetchObject(atDocumentPathWithkey: kFace_Id)
bottomView.lastReportButton.isHidden = (faceId == nil)
addPopView() // 添加首次展示提示框
} else if cameraType == .geneMemory { // 面孔起源
bottomView.lastReportButton.isHidden = false
geneMemoryGenderSelectView.isHidden = false
bottomView.albumButton.isEnabled = false
bottomView.shotButton.isEnabled = false
self.navigationBar.rightButton.isEnabled = false
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let status = AVCaptureDevice.authorizationStatus(for: AVMediaType.video)
if status == .denied {
alertCameraTips()
return
}
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
self.session.stopRunning()
distanceNotRightSetStatus()
faceStatePromter.stopPlayVoice()
setOutputOrigenVolume()
}
// MARK: - 辅助方法
// 返回按钮
override func backAction(_ button: UIButton) {
super.backAction(button)
Phobos.track("on_click_button", attributes: ["page_name": pageName, "tab_name": self.getTabName(), "button_name": "return"])
}
// 通知传值
func addNotification() {
NotificationCenter.default.addObserver(self, selector: #selector(chageCameraStatus), name: NSNotification.Name(rawValue: "GMTestSkinAnimationWebViewSenBack"), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(chageCameraTofaceSkin), name: NSNotification.Name(rawValue: "GMFaceInfoSharePopToFaceSkin"), object: nil)
}
// 拍完照片以后进行跳转
@objc func pushAction() {
let image = Platform.isSimulator ? UIImage(named: "faceAppNormal") : UIImage(data: DataHelper.SharedDataHelper.imageData)
let skinVC = GMTestSkinAnimationWebView()
skinVC.image = image!
AppDelegate.navigation.pushViewController(skinVC, animated: true)
Phobos.track("on_click_button", attributes: ["page_name": "face_scan", "tab_name": "AI测肤", "button_name": "take_pic"])
}
// 切换到AI扫脸
@objc func chageCameraStatus() {
bottomView.selectedType = .scanFace
faceBottomCellDidSelected(.scanFace)
}
// 切换到AI测肤
@objc func chageCameraTofaceSkin() {
bottomView.selectedType = .scanFace
faceBottomCellDidSelected(.scanSkin)
}
func addPopView() {
if isShowNewGuide(key: "NewFaceShotController", count: 3) {
let popView = GMFaceShotPopView()
view.addSubview(popView)
}
}
internal override func rightButtonClicked(_ button: UIButton) {
if Platform.isSimulator { return }
changeCamera()
}
// 上次报告
func fetchLastReport() {
if cameraType == .scanSkin {
// 判断是否展示上次报告按钮
let skinLastReport = GMCache.fetchObject(atDocumentPathWithkey: "GMTestSkinIsShowData")
let showSkinLastReport: Bool = (skinLastReport != nil || (haveSkinLastReport ?? false))
bottomView.lastReportButton.isHidden = !showSkinLastReport
if showSkinLastReport {
Phobos.track("report_status", attributes: ["page_name": pageName, "type": "test"])
isTestTrack = true
}
} else if cameraType == .scanFace {
let faceId = GMCache.fetchObject(atDocumentPathWithkey: kFace_Id)
bottomView.lastReportButton.isHidden = (faceId == nil)
if faceId != nil {
Phobos.track("report_status", attributes: ["page_name": pageName, "type": "scan"])
isScanTrack = true
}
} else if cameraType == .geneMemory {
let showDataStr = GMCache.fetchObject(atDocumentPathWithkey: kGMGeneMemoryLastData)
bottomView.lastReportButton.isHidden = (showDataStr == nil)
}
}
func stringValueDic(_ str: String) -> [String: Any]? {
let data = str.data(using: String.Encoding.utf8)
if let dict = try? JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers)as? [String: Any] {
return dict
}
return Dictionary()
}
// 获取tabname
func getTabName() -> String {
if cameraType == .scanSkin {
return "AI测肤"
} else if cameraType == .scanFace {
return "AI测脸"
} else if cameraType == .geneMemory {
return "AI面孔"
}
return ""
}
// MARK: - 照像机主要类及实现
func setUpCamera () {
// 判断是否可以使用照相机
if !self.canUseCamera() { return }
if Platform.isSimulator { return }
if cameraType == .scanSkin { // skinGray = post 此时为后置摄像头, pre为前置摄像头
cameraDirection = (skinGray == "post") ? .isCameraBack : .isCameraFront
}
let position: AVCaptureDevice.Position = (cameraDirection == .isCameraFront) ? AVCaptureDevice.Position.front : AVCaptureDevice.Position.back
// 使用AVMediaTypeVideo 指明self.device代表视频,默认使用后置摄像头进行初始化
self.device = AVCaptureDevice.default(for: AVMediaType.video)
// 尝试获取摄像头方向
for device in AVCaptureDevice.devices(for: AVMediaType.video) where (device as AnyObject).position == position {
self.device = device
break
}
//使用设备初始化输入
do {
self.input = try AVCaptureDeviceInput(device: self.device)
} catch _ {}
//生成会话,用来结合输入输出
if self.session.canSetSessionPreset(AVCaptureSession.Preset.photo) {
self.session.sessionPreset = AVCaptureSession.Preset.photo
}
// 添加输入对象
if self.session.canAddInput(self.input) {
self.session.addInput(self.input)
}
// 生成输出对象
self.output = AVCaptureVideoDataOutput()
self.output.videoSettings = [kCVPixelBufferPixelFormatTypeKey as AnyHashable as! String: Int(kCVPixelFormatType_32BGRA)]
self.output.alwaysDiscardsLateVideoFrames = true
self.videoDataOutputQueue = DispatchQueue(label: "VideoDataOutputQueue", attributes: [])
self.output.setSampleBufferDelegate(self, queue: self.videoDataOutputQueue)
if self.session.canAddOutput(self.output) {
self.session.addOutput(self.output)
}
//使用self.session,初始化预览层,self.session负责驱动input进行信息的采集,layer负责把图像渲染显示
self.previewLayer = AVCaptureVideoPreviewLayer(session: self.session)
self.previewLayer.frame = CGRect(x: 0, y: 0, width: Constant.screenWidth, height: Constant.screenHeight - UIView.safeAreaInsetsBottom - 167)
self.previewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
// 拍照时要用
imageOutPut!.outputSettings = [AVVideoCodecKey: AVVideoCodecJPEG]
if self.session.canAddOutput(self.imageOutPut!) {
self.session.addOutput(self.imageOutPut!)
}
self.view!.layer.insertSublayer(self.previewLayer, at: 0)
//开始启动
self.session.startRunning()
// 设置闪光灯
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
self.changeCameraFlash(flashMode: (self.cameraType == .scanSkin) ? .on : .off)
}
}
func changeCamera() {
if useCameraTips() { return}
let cameraCount = AVCaptureDevice.devices(for: AVMediaType.video).count
if cameraCount <= 1 { return }
var newCamera: AVCaptureDevice?
var newInput: AVCaptureDeviceInput?
faceStatePromter.initialAudioPlayer.stop() // 点击切换照相机时, 先停止本次声音
if input.device.position == .front {
newCamera = self.cameraWithPosition(.back)
cameraDirection = .isCameraBack
} else {
newCamera = self.cameraWithPosition(.front)
cameraDirection = .isCameraFront
}
do {
newInput = try AVCaptureDeviceInput(device: newCamera!)
} catch let error {
print("toggle carema failed, error = \(error)")
}
if newInput != nil {
session.beginConfiguration()
session.removeInput(input)
if session.canAddInput(newInput!) {
session.addInput(newInput!)
input = newInput
device = newCamera
} else {
session.addInput(self.input)
}
session.commitConfiguration()
}
// 设置相机后置时的测肤声音提示 此处需要设置延时, 不然语音提示会设置不及时
if cameraType == .scanSkin { // 如果是测肤tab情况下 切换摄像头需要处理的事务
skinAIView.isHidden = (cameraDirection == .isCameraFront) ? true : false
skinFrontAIView.isHidden = (cameraDirection == .isCameraFront) ? false : true
Phobos.track("on_click_button", attributes: ["page_name": pageName, "tab_name": "肤质分析", "button_name": "switch_camera"])
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
self.setFaceStateInitial()
self.changeCameraFlash(flashMode: .on)
}
}
}
// 拍摄照片
@objc func shotAction() {
if Platform.isSimulator { return }
if !canUseCamera() { // 如果不可以使用照相机
alertCameraTips()
return
}
Phobos.track("on_click_button", attributes: ["page_name": pageName, "tab_name": self.getTabName(), "button_name": "take_pic"])
let videoConnection = self.imageOutPut!.connection(with: AVMediaType.video)
if videoConnection == nil {
print("take photo failed!")
return
}
imageOutPut!.captureStillImageAsynchronously(from: videoConnection!) { (imageDataSampleBuffer: CMSampleBuffer?, error: Error?) in
if imageDataSampleBuffer == nil { return }
if error != nil {
self.toast("拍摄失败,重新拍摄试试")
return
}
guard let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(imageDataSampleBuffer!) else { return }
guard let image = UIImage(data: imageData) else {
return
}
self.session.stopRunning()
UIImageWriteToSavedPhotosAlbum(image, self, #selector(NewFaceShotController.image(_:didFinishSavingWithError:contextInfo:)), nil)
var result = image
if self.device.position == .front {
result = UIImage(cgImage: image.cgImage!, scale: image.scale, orientation: .leftMirrored)
}
if self.cameraType == .geneMemory {
let upload = GMGeneMemoryAnimationViewController()
upload.image = result
upload.gender = self.selectedGender
self.pushViewController(upload)
} else {
let upload = GMScanAnimationController()
upload.image = result
self.pushViewController(upload)
}
}
}
// 改变摄像头方向
func cameraWithPosition(_ position: AVCaptureDevice.Position) -> AVCaptureDevice? {
let devices = AVCaptureDevice.devices(for: AVMediaType.video)
for device in devices where (device as AnyObject).position == position {
return device
}
return nil
}
// MARK: - 代理方法
func faceBottomCellDidSelected(_ type: GMScanAnimationType) {
cameraType = type
if useCameraTips() { return} // 判断相机权限
distanceNotRightSetStatus()
if type == .scanSkin {
isnoCheckFace = false
voiceView.isHidden = false
geneMemoryGenderSelectView.isHidden = true
self.navigationBar.rightButton.isEnabled = true
skinFrontAIView.isHidden = (cameraDirection == .isCameraFront) ? false : true
skinAIView.isHidden = (cameraDirection == .isCameraBack) ? false : true
setFaceStateInitial()
isSkinShowNewGuide() // 判断测肤是否展示弹框提醒
// 判断上次报告是否展示
let skinLastReport = GMCache.fetchObject(atDocumentPathWithkey: "GMTestSkinIsShowData")
let showSkinLastReport: Bool = (skinLastReport != nil || (haveSkinLastReport ?? false))
bottomView.lastReportButton.isHidden = !showSkinLastReport
if (isTestTrack != true) && showSkinLastReport {
Phobos.track("report_status", attributes: ["page_name": pageName, "type": "test"])
isTestTrack = true
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
self.changeCameraFlash(flashMode: .on) // 设置闪光灯
}
} else if type == .scanFace { // 测脸
isnoCheckFace = true
voiceView.isHidden = true
skinFrontAIView.isHidden = true
skinAIView.isHidden = true
geneMemoryGenderSelectView.isHidden = true
bottomView.albumButton.isEnabled = true
bottomView.shotButton.isEnabled = true
self.navigationBar.rightButton.isEnabled = true
addPopView()
// 判断上次报告是否展示
let faceId = GMCache.fetchObject(atDocumentPathWithkey: kFace_Id)
bottomView.lastReportButton.isHidden = (faceId == nil)
if (isScanTrack != true) && (faceId != nil) {
Phobos.track("report_status", attributes: ["page_name": pageName, "type": "scan"])
isScanTrack = true
}
faceStatePromter.stopPlayVoice() // 停止音频播放
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
self.changeCameraFlash(flashMode: .off)
}
} else if type == .geneMemory { // 面孔起源
isnoCheckFace = true
voiceView.isHidden = true
skinFrontAIView.isHidden = true
skinAIView.isHidden = true
geneMemoryGenderSelectView.isHidden = false
bottomView.albumButton.isEnabled = false
self.navigationBar.rightButton.isEnabled = false
// 判断上次报告是否展示
let showDataStr = GMCache.fetchObject(atDocumentPathWithkey: kGMGeneMemoryLastData)
bottomView.lastReportButton.isHidden = (showDataStr == nil)
faceStatePromter.stopPlayVoice() // 停止音频播放
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
self.changeCameraFlash(flashMode: .off)
}
}
}
// GMGeneMemoryGenderSelectViewDelegate
func didClickGender(_ gender: Int) {
bottomView.albumButton.isEnabled = true
bottomView.shotButton.isEnabled = true
self.navigationBar.rightButton.isEnabled = true
self.selectedGender = gender;
}
// 开关按钮, 及时停止声音
func switchViewDidSelected(isON: Bool) {
if isON == false {
faceStatePromter.stopPlayVoice()
}
}
// 判断测肤是否展示弹框提醒
func isSkinShowNewGuide() {
// 判断弹框提示
if isShowNewGuide(key: "SkinTeachPopView", count: 1) {
isnoCheckFace = true
let skinpopview = GMTestSkinTeachPopView()
view.addSubview(skinpopview)
} else if isShowNewGuide(key: "SkinGuidancePopView", count: 1) {
isnoCheckFace = true
let guidancePopview = GMSkinGuidancePopView()
guidancePopview.guidanceDelegate = self
view.addSubview(guidancePopview)
} else {
setFaceStateInitial() // 设置测肤声音提示
}
}
// MARK: - 相册 action
@objc func image(_ image: UIImage, didFinishSavingWithError error: NSError?, contextInfo: UnsafeMutableRawPointer) {}
@objc func ablumAction() {
Phobos.track("on_click_button", attributes: ["page_name": pageName, "tab_name": self.getTabName(), "button_name": "album"])
let toController = GMPhotoPickController(maxImageCount: 1, maxVideoCount: 0, maxCount: 1, photoDisplayType: GMPhotosDisplayImageType)
toController.dismissDelegate = self
toController.root.showEdit = false
toController.root.singleSelection = true
toController.modalPresentationStyle = UIModalPresentationStyle.fullScreen
present(toController, animated: true, completion: nil)
}
// 关闭浮层框弹框代理
func guidanceBtnClosePopView() {
isnoCheckFace = false
setFaceStateInitial() // 设置测肤声音提示
}
func dismissPhotoPick(_ infos: [GMEditPhotoInfo]) {
if infos.count > 0 {
let model: GMEditPhotoInfo = infos.first!
if self.cameraType == .geneMemory {
geneMemoryGenderSelectView.isHidden = true
let upload = GMGeneMemoryAnimationViewController()
upload.image = model.finshedImage
upload.gender = selectedGender
pushViewController(upload)
} else {
let upload = GMScanAnimationController()
upload.image = model.finshedImage
pushViewController(upload)
}
}
}
// 停止音频播放,并进行后置摄像头音频引导提示
func setFaceStateInitial() {
if (cameraType == .scanSkin) && (cameraDirection == .isCameraBack) {
faceStatePromter.initialAudioPlayer.stop()
faceStatePromter.state = FaceState.initial
}
}
// 是否可以使用照相机
func canUseCamera() -> Bool {
let status = AVCaptureDevice.authorizationStatus(for: AVMediaType.video)
if status == .denied {
return false
} else if status == .notDetermined {
AVCaptureDevice.requestAccess(for: AVMediaType.video, completionHandler: { (result) in
if result {
uiThread {
self.setUpCamera()
}
}
})
return false
} else {
return true
}
}
// MARK: 页面基本设置
// 设置人脸识别特别及音量
func setUpFaceDetector() {
voiceNum = AVAudioSession.sharedInstance().outputVolume
var faceDetectorOptions: [String: String]?
faceDetectorOptions = [CIDetectorAccuracy: CIDetectorAccuracyHigh as String]
self.faceDetector = CIDetector(ofType: CIDetectorTypeFace, context: nil, options: faceDetectorOptions)
}
func addSkinAIViewFrontUI() {
skinFrontAIView = GMFaceSkinFrontView()
skinFrontAIView.frame = self.view.frame
skinFrontAIView.isHidden = true
view.addSubview(skinFrontAIView)
}
func addGeneMemoryGenderView() {
geneMemoryGenderSelectView = GMGeneMemoryGenderSelectView()
geneMemoryGenderSelectView.frame = self.view.frame
geneMemoryGenderSelectView.isHidden = true
geneMemoryGenderSelectView.selectDelegate = self
view.addSubview(geneMemoryGenderSelectView)
}
func addSkinAIViewBackUI() {
skinAIView = GMFaceSkinView()
skinAIView.frame = self.view.frame
skinAIView.isHidden = true
view.addSubview(skinAIView)
}
func addVoiceView() {
voiceView = GMVoiceSwitchView()
voiceView.frame = self.view.frame
voiceView.delegate = self
voiceView.isHidden = true
view.addSubview(voiceView)
voiceView.snp.makeConstraints { (make) in
make.top.equalTo(OCNavigationBar.barHeight + 40)
make.right.equalTo(-20)
make.size.equalTo(CGSize(width: 150, height: 150))
}
}
func addBottomView() {
bottomView = GMFaceBottomView()
bottomView.shotButton.addTarget(self, action: #selector(NewFaceShotController.shotAction), for: .touchUpInside)
bottomView.albumButton.addTarget(self, action: #selector(NewFaceShotController.ablumAction), for: .touchUpInside)
bottomView.delegate = self
view.addSubview(bottomView)
GMNewFaceTool.latestImage { (image) in
if image != nil {
DispatchQueue.main.async {
self.bottomView.albumButton.faceImageView.image = image
}
}
}
bottomView.snp.makeConstraints { (make) in
make.bottom.right.left.equalToSuperview()
make.height.equalTo(167 + UIView.safeAreaInsetsBottom)
}
}
deinit {
NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: "GMTestSkinAnimationWebViewSenBack"), object: nil)
NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: "GMFaceInfoSharePopToFaceSkin"), object: nil)
print("NewFaceShotController--delloc")
}
}
//
// NewFaceShotControllrtExtentsion.swift
// Gengmei
//
// Created by 刘鹿杰 on 2019/11/7.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
import Foundation
// 封装照相机的相关方法
@objc extension NewFaceShotController {
// 输出音量设置为系统的 0.6
func setOutputVolume() {
//没有声音的时候设置声音
do{
try? AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try? AVAudioSession.sharedInstance().setActive(true)
}
if voiceNum < 0.6 {
UserDefaults.standard.set(0.6, forKey: "humanVoice")
DaiVolume.setVolume(0.6)
} else {
UserDefaults.standard.set(AVAudioSession.sharedInstance().outputVolume, forKey: "humanVoice")
DaiVolume.setVolume(AVAudioSession.sharedInstance().outputVolume)
}
}
// 闪光灯设置
func changeCameraFlash(flashMode: AVCaptureDevice.FlashMode) {
do {
try self.device.lockForConfiguration()
} catch _ {
print("lockForConfiguration failed")
}
if self.device.isFlashModeSupported(flashMode) {
self.device.flashMode = flashMode
}
self.device.unlockForConfiguration()
}
// 设置为原始音量
func setOutputOrigenVolume() {
guard (voiceNum != nil) else { return }
DaiVolume.setVolume(voiceNum)
}
// 相机权限提醒
func useCameraTips() -> Bool {
let status = AVCaptureDevice.authorizationStatus(for: AVMediaType.video)
if !canUseCamera() && status != .notDetermined{
alertCameraTips()
return true
}
return false
}
// 相机权限未开启提示框
func alertCameraTips() {
let alert = UIAlertController.showCustomAlert(withTitle: "请前往设置开启摄像权限", message: "")
alert.addAction("取消", actionHandler: {
self.navigationController?.popViewController(animated: true)
})
alert.addAction("去设置", actionHandler: {
UIApplication.shared.openURL(URL(string: UIApplicationOpenSettingsURLString)!)
})
}
func imageFormSampleBuffer(_ sampleBuffer:CMSampleBuffer) {
let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)
CVPixelBufferLockBaseAddress(imageBuffer!, CVPixelBufferLockFlags(rawValue: CVOptionFlags(0)))
let height = CVPixelBufferGetHeight(imageBuffer!)
imgWidth = CGFloat(height)
}
func saveToFileSystem(_ imageData: Data) {
let dataProvider = CGDataProvider(data: imageData as CFData)
var cgImage = CGImage(jpegDataProviderSource: dataProvider!, decode: nil, shouldInterpolate: true, intent: CGColorRenderingIntent.defaultIntent)!
if cgImage.width > 3264 {
cgImage = resizeCGImageBySize(cgImage: cgImage, width: 3264, height: 2448)
}
let takenImage = UIImage(cgImage: cgImage, scale: 1.0, orientation: UIImage.Orientation.up)
let rotatedImage = takenImage.imageRotatedByDegrees(90, flip: false)
let date = Date()
// 存exif信息
let container = ExifContainer()
container.addCreationDate(Date.init(timeIntervalSinceNow: 0))
container.addExifDictionary(self.exif as? [AnyHashable : Any])
let imagedataWithExif = rotatedImage.addExif(container)
let formatter = DateFormatter()
formatter.dateFormat = "yyyyMMdd_HHmmss"
timeString = formatter.string(from: date)
if imagedataWithExif.count == 0 {
DataHelper.SharedDataHelper.imageData = UIImageJPEGRepresentation(rotatedImage, 0.4)!
} else {
DataHelper.SharedDataHelper.imageData = imagedataWithExif
}
}
// MARK: resize CGimage
func resizeCGImageBySize(cgImage: CGImage, width: Int, height: Int) -> CGImage {
let bitsPerComponent = cgImage.bitsPerComponent
let bytesPerRow = cgImage.bytesPerRow
let colorSpace = cgImage.colorSpace
let bitmapInfo = cgImage.bitmapInfo
let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace!, bitmapInfo: bitmapInfo.rawValue)
context!.interpolationQuality = CGInterpolationQuality.high
context?.draw(cgImage, in: CGRect(origin: CGPoint.zero, size: CGSize(width: CGFloat(width), height: CGFloat(height))))
let cgImage = context?.makeImage()
return cgImage!
}
// 实时判断人脸
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
if (isnoCheckFace || beginToTakePicture) { return }
guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {return}
let sourceImage = CIImage(cvPixelBuffer: imageBuffer)
_ = imageFormSampleBuffer(sampleBuffer)
options = [CIDetectorSmile : false as AnyObject, CIDetectorEyeBlink: true as AnyObject, CIDetectorImageOrientation : 6 as AnyObject]
// MARK: - 识别图像人脸
guard let face = self.faceDetector else {return}
let features = face.features(in: sourceImage, options: options)
// 未检测到人脸
DispatchQueue.main.async {
if (features.count == 0) {
self.distanceNotRightSetStatus()
self.faceStatePromter.stopPlayVoice()
self.skinFrontAIView.tipsLaber.text = ""
self.skinFrontAIView.closeEyeLabel.isHidden = true
}
}
if (features.count != 0) { // 如果等于0 就是未检测到人脸
let feature = features[0] as! CIFaceFeature
let faceWidth = feature.bounds.width
var imgWidthMin = imgWidth * 0.6
var imgWidthMax = imgWidth * 0.76
if cameraDirection == .isCameraFront { // 前置摄像头的话,缩小光圈
imgWidthMin = imgWidth * 0.5
imgWidthMax = imgWidth * 0.66
}
if faceWidth > imgWidthMin && faceWidth < imgWidthMax {
getFace += 1
farFace = 0
nearFace = 0
rightFace += 1
}else if faceWidth < imgWidthMin{
getFace = 0
farFace += 1
nearFace = 0
rightFace = 0
}else if faceWidth > imgWidthMax {
getFace = 0
nearFace += 1
farFace = 0
rightFace = 0
}
switch UIDevice.current.modelName {
case "iPhone 5","iPhone 5c","iPhone 5s","iPhone 6","iPhone 6 Plus":
faceStatePromterType(distancecount: 2)
default:
faceStatePromterType(distancecount: 3)
}
if (self.getFace > 2 && !self.beginToTakePicture && !self.didTakePicture && distanceRight && isDelayShot) {
isDelayShot = false
DispatchQueue.main.async {
self.perform(#selector(self.distanceRightAndCloseEyes), with: nil, afterDelay: 5.0)
}
}
}
}
// 设置距离不合适时的状态
func distanceNotRightSetStatus() {
NSObject.cancelPreviousPerformRequests(withTarget: self, selector:#selector(self.distanceRightAndCloseEyes), object: nil)
self.isDelayShot = true
self.distanceRight = false
}
// 距离合适且已经闭眼 符合拍照模式
func distanceRightAndCloseEyes(){
if (getFace > 2 && !beginToTakePicture && !didTakePicture && distanceRight) {
self.isDelayShot = false
self.beginToTakePicture = true
self.faceStatePromter.initialAudioPlayer.stop()
self.captureImage()
self.device.unlockForConfiguration()
self.setHumanVoice()
} else {
self.isDelayShot = true
}
}
// 控制近一点、远一点、距离合适等操作
func faceStatePromterType(distancecount:Int) {
if cameraDirection == .isCameraFront {
DispatchQueue.main.async {
self.skinFrontAIView.closeEyeLabel.isHidden = true
if self.farFace >= distancecount{
self.distanceNotRightSetStatus()
self.skinFrontAIView.tipsLaber.text = "【近一点】"
self.skinFrontAIView.tipsLaber.textColor = UIColor(hex: 0xEC4363).alpha(1.0)
self.faceStatePromter.state = FaceState.far
}
if self.nearFace >= distancecount {
self.distanceNotRightSetStatus()
self.skinFrontAIView.tipsLaber.text = "【远一点】"
self.skinFrontAIView.tipsLaber.textColor = UIColor(hex: 0xEC4363).alpha(1.0)
self.faceStatePromter.state = FaceState.near
}
if self.rightFace >= distancecount {
self.distanceRight = true
self.skinFrontAIView.tipsLaber.text = "【距离合适】"
self.skinFrontAIView.closeEyeLabel.isHidden = false
self.skinFrontAIView.tipsLaber.textColor = UIColor(hex: 0x4ABAB4).alpha(1.0)
if (!self.beginToTakePicture && !self.didTakePicture) {
self.faceStatePromter.state = FaceState.right
}
}
}
} else {
if farFace >= distancecount{
self.distanceRight = false
faceStatePromter.state = FaceState.far
}
if nearFace >= distancecount{
self.distanceRight = false
faceStatePromter.state = FaceState.near
}
if rightFace >= distancecount {
self.distanceRight = true
if (!self.beginToTakePicture && !self.didTakePicture) {
faceStatePromter.state = FaceState.right
}
}
}
}
func setHumanVoice() {
didSetHuman = false
didSetNoHumam = false
if !UserDefaults.standard.bool(forKey: "noHumanVoice") {
// 人声 戴耳机 拍照后设置音量
if isEarPhone() {
if AVAudioSession.sharedInstance().outputVolume > 0.6 {
UserDefaults.standard.set(0.6, forKey: "humanVoice_earPhone")
}else if AVAudioSession.sharedInstance().outputVolume < 0.1 {
UserDefaults.standard.set(0.1, forKey: "humanVoice_earPhone")
}else {
UserDefaults.standard.set(AVAudioSession.sharedInstance().outputVolume, forKey: "humanVoice_earPhone")
}
} else {
// 人声 无耳机 拍照后设置音量
if AVAudioSession.sharedInstance().outputVolume > 0.9 {
UserDefaults.standard.set(0.9, forKey: "humanVoice")
}else if AVAudioSession.sharedInstance().outputVolume < 0.3 {
UserDefaults.standard.set(0.3, forKey: "humanVoice")
}else {
UserDefaults.standard.set(AVAudioSession.sharedInstance().outputVolume, forKey: "humanVoice")
}
}
} else {
if isEarPhone() {
if AVAudioSession.sharedInstance().outputVolume > 0.6 {
UserDefaults.standard.set(0.6, forKey: "doVoice_earPhone")
}else if AVAudioSession.sharedInstance().outputVolume < 0.1 {
UserDefaults.standard.set(0.1, forKey: "doVoice_earPhone")
}else {
UserDefaults.standard.set(AVAudioSession.sharedInstance().outputVolume, forKey: "doVoice_earPhone")
}
} else {
if AVAudioSession.sharedInstance().outputVolume < 0.3 {
UserDefaults.standard.set(0.3, forKey: "duVoice")
}else if AVAudioSession.sharedInstance().outputVolume > 0.9 {
UserDefaults.standard.set(0.9, forKey: "duVoice")
}else {
UserDefaults.standard.set(AVAudioSession.sharedInstance().outputVolume, forKey: "duVoice")
}
}
}
}
// 自动拍照
func captureImage(){
if let videoConnection = imageOutPut!.connection(with: AVMediaType.video) {
videoConnection.videoOrientation = AVCaptureVideoOrientation.portrait
imageOutPut?.captureStillImageAsynchronously(from: videoConnection, completionHandler: {(sampleBuffer, error) in
if (error == nil) {
if (sampleBuffer != nil) {
self.session.stopRunning()
if (CMGetAttachment(sampleBuffer!, kCGImagePropertyExifDictionary, nil) != nil) {
//尝试捕捉
guard let buffer = sampleBuffer else { return }
self.exif = CMGetAttachment(buffer, kCGImagePropertyExifDictionary, nil) as! NSDictionary
}
guard let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(sampleBuffer!) else { return }
self.saveToFileSystem(imageData)
self.didTakePicture = true
self.pushAction() // 跳转拍照页面
}
}
})
}
}
func isEarPhone() -> Bool {
let route = AVAudioSession.sharedInstance().currentRoute
for desc in route.outputs {
if desc.portType == AVAudioSessionPortHeadphones {
return true
}
}
return false
}
}
@objc extension NewFaceShotController {
// 弹框次数限制
func isShowNewGuide(key: String, count: Int) -> Bool {
var dict = GMCache.fetchObject(atDocumentPathWithkey: key)
if dict == nil {
dict = NSMutableDictionary()
}
var appDict = dict as! [String: String]
let version = Bundle.main.infoDictionary!["CFBundleShortVersionString"] as! String
if !appDict.values.contains(version) { // 版本更新了, 删除之前的版本
appDict.removeAll()
}
if appDict.keys.count >= count { // 超过三次,不再弹出提示框
return false
}
let date = Date()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyyMMdd"
let dateStr = dateFormatter.string(from: date)
if !appDict.keys.contains(dateStr) { // 保存数据
appDict[dateStr] = version
GMCache.storeObject(atDocumentPathWithkey: key, object: appDict as NSCoding)
return true
} else {
return false
}
}
}
//
// GMFaceBaseVC.h
// Gengmei
//
// Created by 刘鹿杰 on 2019/7/25.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMDrawDottedView.h"
@class GMFaceCommonPositionModel;
@class GMFaceCommonSubtitleModel;
@class GMFaceCommonContentModel;
@class GMTitleView;
@class ALFaceRectModel;
NS_ASSUME_NONNULL_BEGIN
@interface GMFaceBaseVC : WMBaseViewController
@property (nonatomic, strong) GMImageView *picImageView; // 当前页面图片
@property (nonatomic, strong) GMImageView *titleImageView; // 页面标题imageView
@property (nonatomic, assign) BOOL stopGCD; // 是否停用GCD, 结束当前页面动画
@property (nonatomic, strong) NSMutableArray *allViewLine; // 页面需要消失的线
#pragma mark - 录屏页面第一页 - 画线通用方法
/**
面部分析黄金三角文字展示
*/
-(void)creatFourWord:(NSArray *)triangleArray
addModel:(GMFaceCommonContentModel *)model;
/**
面部分析 画下巴线条
*/
-(void)addChinLine:(NSArray *)chinArray
descModel:(GMFaceCommonContentModel *)descModel;
/**
面部分析 画竖线加描述文字
*/
- (void)creatVorticalDescLine:(NSArray *)pModelArray
content:(GMFaceCommonContentModel *)descModel
offset:(CGFloat)offsetX;
/**
面部分析 画四条横线
*/
- (void)creatHorizontalLine:(NSMutableArray *)pointArray;
#pragma mark - 第五个页面动画 - 画线通用方法
/**
眉眼分析 - 四条虚线箭头带文字
*/
-(void)fiveViewdrawDottodlineWithEye:(NSArray<GMFaceCommonPositionModel *> *)eyeArray
eyebrow:(NSArray<GMFaceCommonPositionModel *> *)eyebroArray
modelAry:(NSArray *)modelAry;
/**
眉眼分析 - 横线和竖线
*/
- (void)fiveViewdrawlineWithEye:(NSArray<GMFaceCommonPositionModel *> *)eyeNewPoint
eyebrow:(NSArray<GMFaceCommonPositionModel *> *)eyebrowNewPoint;
#pragma mark - 第六个页面动画 - 画线通用方法
/**
第六个页面虚线箭头带文字绘画方法
*/
- (void)drawDottodLineWithPointArray:(NSArray<GMFaceCommonPositionModel *> *)pointArray
content:(GMFaceCommonContentModel *)contentModel
type:(GMLineType)lineType
place:(GMWordPlace)place;
/**
第六个页面水平线绘画方法
*/
- (void)drawSixhLine:(NSArray<GMFaceCommonPositionModel *> *)pointXArray
widthArray:(NSArray<NSNumber *> *)widthArray;
/**
第六个页面竖线方法
*/
- (void)drawSixVerticalLine:(NSArray<GMFaceCommonPositionModel *> *)pointXArray
hightArray:(NSArray<NSNumber *> *)hightArray;
#pragma mark - 公用方法
/**
创建带描述文字的竖线 文字在右侧
@param contentModel 描述文字
@param pointArray 画线的点位传两个点过来
*/
-(void)creatVerticalDottedLineWith:(GMFaceCommonContentModel *)contentModel addArray:(NSArray *)pointArray offset:(CGFloat)offset;
/**
计算两点之间的水平宽度
*/
-(CGFloat)calculateWidthWithArray:(NSArray*)array;
/**
返回中心点model
*/
-(GMFaceCommonPositionModel *)calculateCenterModelWithArray:(NSArray*)array;
/**
更新背景图片的尺寸
*/
- (void)updatePicImageViewframeP;
/**
给一个矩形框,返回一个宽和高
*/
- (CGSize)getWidthAddHightWithRect:(NSArray<GMFaceCommonPositionModel *> *)rectArray;
/**
计算中心点坐标
*/
-(NSArray *)midlePointWithModel:(ALFaceRectModel *)model;
/**
计算放大后的点位置
*/
-(NSMutableArray *)bigImagePointWithScale:(CGFloat)scale
offsetX:(CGFloat)offsetX
offsetY:(CGFloat)offsetY
poinyArray:(NSArray *)pointArray;
/**
左侧描述弹出框方法封装
*/
- (void)leftPopTitleWith:(NSMutableArray *)viewLineArray
AddSubtitle:(NSArray *)subTitle
Animation:(CGFloat)duration;
/**
添加描述文字单个小View
*/
- (GMTitleView *)addDectTitleNameWithModel:(GMFaceCommonSubtitleModel *)tempModel
viewFrame:(CGRect)frame;
/**
创建黄金三角类似的文字
*/
- (GMLabel *)creatLabelWithText:(NSString *)text
Font:(CGFloat)font
Color:(UIColor *)color
Size:(CGRect)frame;
/**
画线方法
*/
- (CAShapeLayer *)drawLineMethod:(NSArray<GMFaceCommonPositionModel *>*)positionModelArray
Color:(UIColor *)color;
/**
清除页面线条View
*/
- (void)clearFristViewLineView;
@end
NS_ASSUME_NONNULL_END
//
// GMFaceBaseVC.m
// Gengmei
//
// Created by 刘鹿杰 on 2019/7/25.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMFaceBaseVC.h"
#import "GMFaceV3Model.h"
#import "GMTitleView.h"
#import "GMDrawDottedView.h"
#import "GMImageView+GMFace.h"
#define AnimationDuration 0.35 // 动画执行时间
#define TitleToTopHight [OCNavigationBar barHeight] + 101
#define Blue_line [UIColor colorWithHexString:@"0x4EC4CD" alpha:0.6]
@interface GMFaceBaseVC ()
@end
@implementation GMFaceBaseVC
// 面部分析黄金三角文字展示
-(void)creatFourWord:(NSArray *)triangleArray addModel:(GMFaceCommonContentModel *)model {
GMFaceCommonPositionModel *triangleL = triangleArray[0]; // 获取黄金三角最后一个点
GMFaceCommonPositionModel *triangleR = triangleArray[1]; // 获取黄金三角最后一个点
GMFaceCommonPositionModel *triangleP = triangleArray[2]; // 获取黄金三角最后一个点
// 添加黄金三角文字
NSString *title = [NSString stringWithFormat:@"%@\n%@",model.title,model.value];
CGRect titleSize = CGRectMake(triangleP.x - 40, triangleP.y - 60, 80, 35);
GMLabel *dectLabel0 = [self creatLabelWithText:title Font:9 Color:UIColor.whiteColor Size:titleSize];
dectLabel0.numberOfLines = 2;
dectLabel0.alpha = 0.0;
[self.allViewLine addObject:dectLabel0];
// 添加 L 文字
CGRect LSize = CGRectMake(triangleL.x - 15, triangleL.y - 6, 12, 12);
GMLabel *dectLabelL = [self creatLabelWithText:@"L" Font:12 Color:UIColor.whiteColor Size:LSize];
dectLabelL.alpha = 0.0;
[self.allViewLine addObject:dectLabelL];
// 添加 R 文字
CGRect RSize = CGRectMake(triangleR.x + 3, triangleR.y - 6, 12, 12);
GMLabel *dectLabelR = [self creatLabelWithText:@"R" Font:12 Color:UIColor.whiteColor Size:RSize];
dectLabelR.alpha = 0.0;
[self.allViewLine addObject:dectLabelR];
// 添加 P 文字
CGRect PSize = CGRectMake(triangleP.x - 6, triangleP.y + 3, 12, 12);
GMLabel *dectLabelP = [self creatLabelWithText:@"P" Font:12 Color:UIColor.whiteColor Size:PSize];
dectLabelP.alpha = 0.0;
[self.allViewLine addObject:dectLabelP];
[UIView animateWithDuration:AnimationDuration animations:^{
dectLabel0.alpha = 1.0;
dectLabelL.alpha = 1.0;
dectLabelR.alpha = 1.0;
dectLabelP.alpha = 1.0;
}];
}
// 面部分析 画下巴线条
-(void)addChinLine:(NSArray *)chinArray descModel:(GMFaceCommonContentModel *)descModel{
GMFaceCommonPositionModel *point0 = chinArray[1]; // 中心点
GMFaceCommonPositionModel *point1 = chinArray.firstObject; // 左侧点
GMFaceCommonPositionModel *point2 = chinArray.lastObject; // 右侧点
// 延长线条
CGPoint leftEndPoint = CGPointMake(point0.x - 2*(point0.x - point1.x), point0.y - 2*( point0.y - point1.y));
CGPoint rightEndPoint = CGPointMake(point0.x + 2*(point2.x-point0.x), point0.y - 2*( point0.y - point2.y));
GMFaceCommonPositionModel *leftModel = [GMFaceCommonPositionModel creatFaceCommonModelWithCGPoint:leftEndPoint];
GMFaceCommonPositionModel *rightModel = [GMFaceCommonPositionModel creatFaceCommonModelWithCGPoint:rightEndPoint];
// 添加下巴两天线
CALayer *layer1 = [self drawLineMethod:@[point0,leftModel] Color:Blue_line];
CALayer *layer2 = [self drawLineMethod:@[point0,rightModel] Color:Blue_line];
[self.allViewLine addObject:layer1];
[self.allViewLine addObject:layer2];
// 添加下巴角度
// GMFaceCommonContentModel *lastContentModel = contentArray.lastObject;
GMLabel *dectLabel1 = [self creatLabelWithText:descModel.value Font:9 Color:UIColor.whiteColor Size:CGRectMake(point0.x - 30, point0.y - 20, 60, 15)];
[self.allViewLine addObject:dectLabel1];
}
// 面部分析 画竖线加描述文字
- (void)creatVorticalDescLine:(NSArray *)pModelArray content:(GMFaceCommonContentModel *)descModel offset:(CGFloat)offsetX{
GMFaceCommonPositionModel* linePointModel0 = pModelArray[0];
GMFaceCommonPositionModel* linePointModel1 = pModelArray[1];
NSString *descStr = [NSString stringWithFormat:@"%@%@",descModel.title,descModel.value];
CGSize descSize = [descStr sizeWithAttributes:@{NSFontAttributeName:[UIFont gmFont:9]}];
GMDrawDottedView *lineView = [[GMDrawDottedView alloc] initWithLineType:drawViewTypeVerticalDotted addlabel:wordPlaceRight addDect:descStr];
lineView.backgroundColor = UIColor.clearColor;
lineView.alpha = 0;
lineView.frame = CGRectMake(linePointModel0.x + offsetX, linePointModel0.y, descSize.width + 10, linePointModel1.y - linePointModel0.y);
[self.view addSubview:lineView];
[self.allViewLine addObject:lineView];
[UIView animateWithDuration:AnimationDuration animations:^{
lineView.alpha = 1.0;
}];
}
// 面部分析 画四条横线
- (void)creatHorizontalLine:(NSMutableArray *)pointArray {
__weak typeof (self) weakSelf = self;
NSArray *lineWidth = @[@(82),@(82),@(175),@(175),@(284),@(284)];
[pointArray enumerateObjectsUsingBlock:^(GMFaceCommonPositionModel * _Nonnull point, NSUInteger idx, BOOL * _Nonnull stop) {
GMImageView *lineImage = [GMImageView creatHorizontalPathWithY:point.y];
lineImage.centerX = point.x;
[weakSelf.view addSubview:lineImage];
[UIView animateWithDuration:AnimationDuration animations:^{
lineImage.width = [lineWidth[idx] floatValue];
lineImage.centerX = point.x;
}];
[weakSelf.allViewLine addObject:lineImage];
}];
}
#pragma mark - 第五个页面动画 - 画线通用方法
// 眉眼分析 - 四条虚线
-(void)fiveViewdrawDottodlineWithEye:(NSArray<GMFaceCommonPositionModel *> *)eyeArray eyebrow:(NSArray<GMFaceCommonPositionModel *> *)eyebroArray modelAry:(NSArray *)modelAry {
ALFaceRectModel *eye = [ALFaceRectModel creatRectModelWithTop:eyeArray[1].y right:eyeArray[2].x left:eyeArray[0].x bottom:eyeArray[3].y];
ALFaceRectModel *eyebrow = [ALFaceRectModel creatRectModelWithTop:eyebroArray[1].y right:eyebroArray[2].x left:eyebroArray[0].x bottom:eyebroArray[3].y];
// 画虚线框
NSArray *type = @[@(0),@(1),@(0),@(1)];
NSArray *pleac = @[@(wordPlaceLeft),@(wordPlaceTop),@(wordPlaceRight),@(wordPlaceBottom)];
for (int i = 0; i < modelAry.count; i++ ) {
GMFaceCommonContentModel *model = modelAry[i];
NSString *dect = [NSString stringWithFormat:@"%@\n%@",model.title,model.value];
CGSize dectSize = [dect sizeWithAttributes:@{NSFontAttributeName:[UIFont gmFont:9]}];
// 文字在中间
GMDrawDottedView *drawHView = [[GMDrawDottedView alloc] initWithLineType:[type[i] intValue] addlabel:[pleac[i] intValue] addDect:dect];
drawHView.backgroundColor = [UIColor clearColor];
CGRect frame;
if (i == 0) {
frame = CGRectMake(eyebrow.left - dectSize.width - 15, eyebrow.top, dectSize.width + 10, eyebrow.bottom - eyebrow.top);
}else if (i == 1) {
frame = CGRectMake(eyebrow.left, eyebrow.top - dectSize.height - 15, eyebrow.right - eyebrow.left, dectSize.height + 10);
}else if (i == 2){
frame = CGRectMake(eye.right + 5, eye.top, dectSize.width + 10, eye.bottom -eye.top);
}else{
frame = CGRectMake(eye.left, eye.bottom + 5, eye.right - eye.left, dectSize.height + 10);
}
drawHView.frame = frame;
drawHView.alpha = 0;
[self.view addSubview:drawHView];
[UIView animateWithDuration:AnimationDuration animations:^{
drawHView.alpha = 1;
}];
[self.allViewLine addObject:drawHView];
}
}
// 眉眼分析 - 横线和竖线
- (void)fiveViewdrawlineWithEye:(NSArray<GMFaceCommonPositionModel *> *)eyeNewPoint eyebrow:(NSArray<GMFaceCommonPositionModel *> *)eyebrowNewPoint {
NSArray *HLinePointArray = @[eyebrowNewPoint[1],eyebrowNewPoint[3],eyeNewPoint[1],eyeNewPoint[3]];
NSArray *VLinePointArray = @[eyebrowNewPoint[0],eyebrowNewPoint[2],eyeNewPoint[0],eyeNewPoint[2]];
// 获取矩形框的宽和高
CGSize eyebrowSize = [self getWidthAddHightWithRect:eyebrowNewPoint];
CGSize eyeSize = [self getWidthAddHightWithRect:eyeNewPoint];
// 画四条横线
NSArray *widthArray = @[@(eyebrowSize.width),@(eyebrowSize.width),@(eyeSize.width),@(eyeSize.width)];
[HLinePointArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
GMFaceCommonPositionModel *model = (GMFaceCommonPositionModel *)obj;
GMImageView *himageView = [GMImageView creatHorizontalPathWithY:model.y];
himageView.centerX = model.x;
himageView.alpha = 0;
[UIView animateWithDuration:AnimationDuration animations:^{
himageView.width = [widthArray[idx] floatValue] + 30;
himageView.alpha = 1;
himageView.centerX = model.x;
}];
[self.view addSubview:himageView];
[self.allViewLine addObject:himageView];
}];
// 画四条竖线
NSArray *hightArray = @[@(eyebrowSize.height),@(eyebrowSize.height),@(eyeSize.height),@(eyeSize.height)];
[VLinePointArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
GMFaceCommonPositionModel *model = (GMFaceCommonPositionModel *)obj;
GMImageView *vimageView = [GMImageView creatVerticalPathWithX:model.x];
vimageView.centerY = model.y;
[self.view addSubview:vimageView];
[UIView animateWithDuration:AnimationDuration animations:^{
vimageView.height = [hightArray[idx] floatValue] + 30;;
vimageView.centerY = model.y;
}];
[self.allViewLine addObject:vimageView];
}];
}
#pragma mark - 第六个页面动画 - 画线通用方法
- (void)drawDottodLineWithPointArray:(NSArray<GMFaceCommonPositionModel *> *)pointArray content:(GMFaceCommonContentModel *)contentModel type:(GMLineType)lineType place:(GMWordPlace)place {
NSString *dect = [NSString stringWithFormat:@"%@\n%@",contentModel.title,contentModel.value];
CGSize dectSize = [dect sizeWithAttributes:@{NSFontAttributeName:[UIFont gmFont:9]}];
GMDrawDottedView *drawHView = [[GMDrawDottedView alloc] initWithLineType:lineType addlabel:place addDect:dect];
drawHView.backgroundColor = [UIColor clearColor];
CGRect frame;
if (lineType == drawViewTypeHorizontalDotted) {
if (place == wordPlaceTop) {
frame = CGRectMake(pointArray[0].x, (pointArray[0].y - dectSize.height - 10),pointArray[1].x - pointArray[0].x, dectSize.height + 10);
}else{
frame = CGRectMake(pointArray[0].x, (pointArray[0].y - 10),pointArray[1].x - pointArray[0].x, dectSize.height + 10);
}
}else{
frame = CGRectMake(pointArray[0].x + 5, pointArray[0].y, dectSize.width + 10, pointArray[1].y - pointArray[0].y);
}
drawHView.frame = frame;
drawHView.alpha = 0;
[self.view addSubview:drawHView];
[UIView animateWithDuration:AnimationDuration animations:^{
drawHView.alpha = 1;
}];
[self.allViewLine addObject:drawHView];
}
- (void)drawSixhLine:(NSArray<GMFaceCommonPositionModel *> *)pointXArray widthArray:(NSArray<NSNumber *> *)widthArray {
for (int i = 0; i < pointXArray.count; i++) {
GMFaceCommonPositionModel *model = pointXArray[i];
GMImageView *vimageView = [GMImageView creatHorizontalPathWithY:model.y];
vimageView.centerX = model.x;
[self.view addSubview:vimageView];
[UIView animateWithDuration:AnimationDuration animations:^{
vimageView.width = [widthArray[i] floatValue];
vimageView.centerX = model.x;
}];
[self.allViewLine addObject:vimageView];
}
}
- (void)drawSixVerticalLine:(NSArray<GMFaceCommonPositionModel *> *)pointXArray hightArray:(NSArray<NSNumber *> *)hightArray {
for (int i = 0; i < pointXArray.count; i++) {
GMFaceCommonPositionModel *model = pointXArray[i];
GMImageView *vimageView = [GMImageView creatVerticalPathWithX:model.x];
vimageView.centerY = model.y;
[self.view addSubview:vimageView];
[UIView animateWithDuration:AnimationDuration animations:^{
vimageView.height = [hightArray[i] floatValue];
vimageView.centerY = model.y;
}];
[self.allViewLine addObject:vimageView];
}
}
#pragma mark - 公用方法
/*
创建带描述文字的竖线 文字在右侧
@param contentModel 描述文字
@param pointArray 画线的点位传两个点过来
*/
-(void)creatVerticalDottedLineWith:(GMFaceCommonContentModel *)contentModel addArray:(NSArray *)pointArray offset:(CGFloat)offset{
GMFaceCommonPositionModel *model0 = pointArray[0];
GMFaceCommonPositionModel *model1 = pointArray[1];
NSString *nbqkString = [NSString stringWithFormat:@"%@%@",contentModel.title,contentModel.value];
CGSize nbqkStringSize = [nbqkString sizeWithAttributes:@{NSFontAttributeName:[UIFont gmFont:9]}];
GMDrawDottedView *nbqkDottedLine = [[GMDrawDottedView alloc] initWithLineType:drawViewTypeVerticalDotted addlabel:wordPlaceRight addDect:nbqkString];
nbqkDottedLine.backgroundColor = UIColor.clearColor;
nbqkDottedLine.alpha = 0;
nbqkDottedLine.frame = CGRectMake(model0.x + offset, model0.y, nbqkStringSize.width + 10, model1.y - model0.y);
[self.view addSubview:nbqkDottedLine];
[self.allViewLine addObject:nbqkDottedLine];
[UIView animateWithDuration:AnimationDuration animations:^{
nbqkDottedLine.alpha = 1;
}];
}
// 计算两点之间的水平宽度
-(CGFloat)calculateWidthWithArray:(NSArray*)array{
GMFaceCommonPositionModel *model0 = array[0];
GMFaceCommonPositionModel *model1 = array[1];
return model1.x - model0.x;
}
// 返回中心点model
-(GMFaceCommonPositionModel *)calculateCenterModelWithArray:(NSArray*)array{
GMFaceCommonPositionModel *centerModel = [GMFaceCommonPositionModel new];
GMFaceCommonPositionModel *model0 = array[0];
GMFaceCommonPositionModel *model1 = array[1];
centerModel.x = (model0.x + model1.x) / 2;
centerModel.y = (model0.y + model1.y) / 2;
return centerModel;
}
// 更新背景图片的尺寸
- (void)updatePicImageViewframeP {
[UIView animateWithDuration:AnimationDuration animations:^{
self.picImageView.frame = self.view.frame;
}];
}
// 给一个矩形框,返回一个宽和高
- (CGSize)getWidthAddHightWithRect:(NSArray<GMFaceCommonPositionModel *> *)rectArray{
NSAssert(rectArray.count == 4, @"这个数组必须传过来四个点");
CGFloat rectWidth = rectArray[2].x - rectArray[0].x;
CGFloat recthight = rectArray[3].y - rectArray[1].y;
return CGSizeMake(rectWidth, recthight);
}
// 计算中心点坐标
-(NSArray *)midlePointWithModel:(ALFaceRectModel *)model {
GMFaceCommonPositionModel *midleLeft = [GMFaceCommonPositionModel creatFaceCommonModelWithX:model.left addY:(model.bottom - model.top) / 2 + model.top];
GMFaceCommonPositionModel *midleTop = [GMFaceCommonPositionModel creatFaceCommonModelWithX:(model.right - model.left) / 2 + model.left addY:model.top];
GMFaceCommonPositionModel *midleRight = [GMFaceCommonPositionModel creatFaceCommonModelWithX:model.right addY:(model.bottom - model.top) / 2 + model.top];
GMFaceCommonPositionModel *midleBottom = [GMFaceCommonPositionModel creatFaceCommonModelWithX:(model.right - model.left) / 2 + model.left addY:model.bottom];
return [NSArray arrayWithObjects:midleLeft,midleTop,midleRight,midleBottom, nil];
}
// 计算放大后的点位置
-(NSMutableArray *)bigImagePointWithScale:(CGFloat)scale offsetX:(CGFloat)offsetX offsetY:(CGFloat)offsetY poinyArray:(NSArray *)pointArray {
NSMutableArray *newPointArray = [NSMutableArray array];
[pointArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
GMFaceCommonPositionModel *point = (GMFaceCommonPositionModel *)obj;
GMFaceCommonPositionModel *newPoint = [GMFaceCommonPositionModel new];
newPoint.x = point.x * scale + offsetX;
newPoint.y = point.y * scale + offsetY;
[newPointArray addObject:newPoint];
}];
return newPointArray;
}
// 左侧描述弹出框方法封装
- (void)leftPopTitleWith:(NSMutableArray *)viewLineArray AddSubtitle:(NSArray *)subTitle Animation:(CGFloat)duration {
__weak typeof (self) weakSelf = self;
for (int i = 0; i < subTitle.count; i++) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((duration + 0.15 * i) * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (weakSelf.stopGCD) { return ;}
GMFaceCommonSubtitleModel *subTitleModel = subTitle[i];
CGFloat sumHight = TitleToTopHight;
for (int j = 0; j < i; j ++) {
GMFaceCommonSubtitleModel *titleModel = subTitle[j];
sumHight += titleModel.hight + 20;
}
GMTitleView *tView = [weakSelf addDectTitleNameWithModel:subTitleModel viewFrame:CGRectMake(-120, sumHight, 120, subTitleModel.hight)];
[viewLineArray addObject:tView];
});
}
}
// 创建黄金三角类似的描述文字
- (GMLabel *)creatLabelWithText:(NSString *)text Font:(CGFloat)font Color:(UIColor *)color Size:(CGRect)frame {
GMLabel *label = [GMLabel labelWithTextAlignment:NSTextAlignmentCenter backgroundColor:UIColor.clearColor textColor:color fontSize:font];
label.shadowColor = [UIColor colorWithHexString:@"000000" alpha:0.3];
label.shadowOffset = CGSizeMake(0, 1);
label.layer.shadowRadius = 0.5;
label.frame = frame;
label.text = text;
[self.view addSubview:label];
return label;
}
// 添加描述文字
- (GMTitleView *)addDectTitleNameWithModel:(GMFaceCommonSubtitleModel *)tempModel viewFrame:(CGRect)frame {
GMTitleView *tView = [[GMTitleView alloc] initWithModel:tempModel];
tView.alpha = 0;
tView.backgroundColor = [UIColor clearColor];
tView.frame = frame;
[self.view addSubview:tView];
[UIView animateWithDuration:AnimationDuration animations:^{
tView.transform = CGAffineTransformMakeTranslation(135, 0);
tView.alpha = 1;
}];
return tView;
}
// 画线方法
- (CAShapeLayer *)drawLineMethod:(NSArray<GMFaceCommonPositionModel *>*)positionModelArray Color:(UIColor *)color{
UIBezierPath *path = [UIBezierPath bezierPath];
[positionModelArray enumerateObjectsUsingBlock:^(GMFaceCommonPositionModel *value, NSUInteger idx, BOOL * _Nonnull stop) {
GMFaceCommonPositionModel *pointModel = value;
if (idx == 0) {
[path moveToPoint:CGPointMake(pointModel.x, pointModel.y)];
}else {
[path addLineToPoint:CGPointMake(pointModel.x, pointModel.y)];
}
}];
// 创建CAShapeLayer
CAShapeLayer *layer = [CAShapeLayer layer];
layer.lineWidth = 1.0f;
layer.fillColor = [UIColor clearColor].CGColor;
layer.strokeColor = color.CGColor;
[self.view.layer addSublayer:layer];
layer.path = path.CGPath;
// 创建Animation
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
animation.fromValue = @(0.0);
animation.toValue = @(1.0);
layer.autoreverses = NO;
animation.duration = 0.75f;
// 设置layer的animation
[layer addAnimation:animation forKey:nil];
return layer;
}
// 清除页面上的线条
- (void)clearFristViewLineView {
for (id viewOrLayer in self.allViewLine) {
if ([viewOrLayer isKindOfClass:[UIView class]]) {
UIView *view = (UIView *)viewOrLayer;
[UIView animateWithDuration:AnimationDuration animations:^{
view.alpha = 0;
} completion:^(BOOL finished) {
[view removeAllSubviews];
}];
}else if([viewOrLayer isKindOfClass:[CALayer class]]){
CALayer *layer = (CALayer *)viewOrLayer;
[CATransaction begin];
[CATransaction setAnimationDuration:AnimationDuration];
layer.opacity = 0.0;
[CATransaction commit];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(AnimationDuration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[layer removeFromSuperlayer];
});
}
}
}
-(NSMutableArray *)allViewLine{
if (!_allViewLine) {
_allViewLine = [NSMutableArray array];
}
return _allViewLine;
}
@end
//
// GMFaceInfoShareViewController.h
// Gengmei
//
// Created by Locus on 2019/6/5.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMBaseWebViewController.h"
static NSString * const cellID = @"cell";
#define ShareImageHeight (MAINSCREEN_HEIGHT - UIView.safeAreaInsetsBottom - 206 - self.navigationBar.bottom)
#define ItemMargin 20
#define btnBottomMargin - (23 + UIView.safeAreaInsetsBottom)
@interface GMFaceShareCell : GMCollectionViewCell
@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, strong) UIImage *image;
@end
@interface GMFaceShareFlowLayout : UICollectionViewFlowLayout
@end
@class GMFaceV3Model;
@interface GMFaceInfoShareViewController : GMBaseWebViewController
@property (nonatomic, strong) GMFaceV3Model *faceModel;
@end
//
// GMFaceInfoShareViewController.m
// Gengmei
//
// Created by Locus on 2019/6/5.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMFaceInfoShareViewController.h"
#import "ALFaceInfoViewModel.h"
#import "ALShareSheet.h"
#import "GMFaceReplayViewController.h"
#import "GMFaceReplayModel.h"
#import "GMFaceV3Model.h"
#import "GMPhotoPickController.h"
#import "GMFaceV3Util.h"
#import "GMSignTask.h"
#import "Gengmei-Swift.h"
#define isGray GMLaunchManager.shareManager.appConfigObject.aiScanFaceGray
@interface GMFaceInfoShareViewController () <WKWebViewDelegate,WKNavigationDelegate, GMPhotoPickDismissDelegate>
@property (nonatomic, strong) ALShareSheet *shareSheet;
@property (nonatomic, strong) WMShareObject *shareObj;
@property (nonatomic, strong) GMImageView *showImageView; // 分享专用imageView
@property (nonatomic, strong) GMImageView *bgImageView;
@property (nonatomic, strong) GMImageView *bgNograyImageView; // 非灰度背景图片
@property (nonatomic, strong) UIImage *shareImage; // 带小程序码的图片(默认分享图)
@property (nonatomic, strong) UIImage *ewmImage; // 带二维码的图片
@property (nonatomic, strong) GMPlayerView *playerView; // 视频播放器
@property (nonatomic, strong) AVURLAsset *playerURL; // 视频播放的URL
@property (nonatomic, strong) UIView *bottomView;
@property (nonatomic, copy) NSString *imUrl;
@property (nonatomic, assign) BOOL webLoadSucceed;
@end
@implementation GMFaceInfoShareViewController
- (void)initController {
[super initController];
self.pageName = @"report_result";
self.navigationBar.backgroundColor = isGray ? RGBCOLOR_HEX(0xE9FCFC) : UIColor.clearColor;
self.navigationBar.isShowShadow = NO;
self.navigationBar.title = @"颜值报告";
self.navigationBar.leftIcon = @"back";
self.navigationBar.rightIcon = @"face_right_share_image";
self.navigationBar.rightButton.hidden = YES;
[self.navigationBar.rightButton setTitleColor: isGray ? UIColor.auxiliaryTextGreen : UIColor.headlineText forState:UIControlStateNormal];
}
- (void)viewDidLoad {
[super viewDidLoad];
if (!self.faceModel) {
NSDictionary *dict = [GMCache fetchObjectAtDocumentPathWithkey:@"GMFaceV3Model"];
NSData *imageData = [GMCache fetchObjectAtDocumentPathWithkey:@"GMFaceV3ModelImage"];
GMFaceV3Model *model = [[GMFaceV3Model alloc] initWithDictionary:dict error:NULL];
model.image = [UIImage imageWithData:imageData];
self.faceModel = model;
}
self.webCompent.delegate = self;
self.webCompent.clientH5Object.delegate = nil;
self.fd_interactivePopDisabled = YES;
self.webCompent.backgroundColor = [UIColor yellowColor];
[self.webCompent mas_updateConstraints:^(MASConstraintMaker *make) {
make.left.right.top.bottom.mas_equalTo(0);
}];
self.path = isGray ? API_ALPHA_FACE_REPORT : API_ALPHA_FACE_REPORT0;
[self reloadURL];
if (isGray) {
[self.view addSubview: self.bgImageView];
}else{
[self.view addSubview: self.bgNograyImageView];
}
self.webCompent.webView.scrollView.delegate = self;
[self addNotification]; // 添加通知
[self uploadSignTaskComplete];// 上传签到任务完成
}
- (void)addNotification{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(faceReplayResultVCNotification:) name:@"GMFaceReplayResultVC" object:nil];
}
- (NSString *)moreQueryParameters {
NSString *faceId = [ALFaceInfoViewModel getLocalFaceId];
return [NSString stringWithFormat:@"&face_id=%@&referrer=%@",faceId,self.pageName];
}
- (void)uploadSignTaskComplete {
[self.navigationController.childViewControllers enumerateObjectsUsingBlock:^(__kindof UIViewController * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([obj isKindOfClass:[NewFaceShotController class]]) {
NewFaceShotController *newFace = (NewFaceShotController *)obj;
if (newFace.haveTask) {
if (newFace.cameraType == GMScanAnimationTypeScanFace) {
[GMSignTask signTaskComplete:GMSignTaskTypeScanFace taskId:@""];
} else {
[GMSignTask signTaskComplete:GMSignTaskTypeTestSkin taskId:@""];
}
newFace.haveTask = NO;
*stop = YES;
}
}
}];
}
#pragma mark - 与前端交互方法
- (NSString *)nativeDataLoaded {
NSDictionary *para = @{@"title_bar_height" : @(OCNavigationBar.barHeight)};
NSData *paraData = [NSJSONSerialization dataWithJSONObject:para options:0 error:NULL];
NSString *paraStr = [[NSString alloc] initWithData:paraData encoding:NSUTF8StringEncoding];
return paraStr;
}
// 保存视频方法
-(void)saveFaceVideo:(NSString *)jsonStr {
NSData *jsonData = [jsonStr dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:nil];
UIImage *shareImage = [self stringToImageWith:jsonStr imageStr:@"shareImg"];
if (!shareImage) {return;}
GMFaceReplayModel *raplayModel = [[GMFaceReplayModel alloc] initWithDictionary:dict error:nil];
raplayModel.type = dict[@"type"]; // preview'(预览)/wechat(录制朋友圈视频)/douyin(录制抖音视频)
raplayModel.dataEwm = dict[@"dataEwm"]; // 二维码图片地址
raplayModel.xcxCode = dict[@"xcxCode"]; // 小程序图片地址
raplayModel.faceType = dict[@"faceType"]; // 脸型
raplayModel.starName = dict[@"starName"]; // 明星脸
raplayModel.paragraph2 = dict[@"paragraph2"]; // 描述内容
raplayModel.resultImage = shareImage;
raplayModel.iconImage = [GMCache fetchObjectAtDocumentPathWithkey:@"scanUserImg"]; // 录屏结果页面头像
GMFaceReplayViewController * vc = [[GMFaceReplayViewController alloc] initWithModel:self.faceModel replayModel:raplayModel];
[self.navigationController pushViewController:vc animated:YES];
}
// 监听通知,刷新页面
- (void)faceReplayResultVCNotification:(NSNotification *)noti {
self.playerURL = [noti object];
// 刷新当前页面
[self.webCompent.webView evaluateJavaScript:@"window.gm.pack.run('saveVideoSuccess')" completionHandler:nil];
}
// 播放视频方法
- (void)playFaceVideo {
if (!self.playerURL) {
[self toast:@"视频无法播放!"];
return;
}
self.playerView = [[GMPlayerView alloc] init];
_playerView.topicId = @"";
_playerView.from = @"";
__weak typeof (self) weakSelf = self;
[_playerView setCloseBtnClick:^{
weakSelf.playerView = nil;
}];
[_playerView playWithAVURLAsset:self.playerURL withRect:CGRectMake(MAINSCREEN_WIDTH / 2, MAINSCREEN_HEIGHT / 2, 1, 1)];
}
- (void)changeAvatar {
GMPhotoPickController *controller = [[GMPhotoPickController alloc] initWithMaxImageCount:1 maxVideoCount:0 maxCount:1 photoDisplayType:GMPhotosDisplayImageType];
controller.dismissDelegate = self;
controller.root.singleSelection = YES;
controller.root.showCropFirst = YES;
[self presentViewController:controller animated:YES completion:NULL];
}
- (void)dismissPhotoPick:(NSArray<GMEditPhotoInfo *> *)infos {
if (infos.count == 0) { return;}
GMEditPhotoInfo *model = infos.firstObject;
UIImage *image = model.finshedImage;
[GMFaceV3Util uploadImageToQiNiu:image successBlock:^(id responseObject) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSString *urlStr = [NSString stringWithFormat:@"https://pic.igengmei.com/%@-aspectscale800", responseObject];
NSString *javaScriptStr = [NSString stringWithFormat:@"window.gm.pack.run('qnIconKey','%@')",urlStr];
[self.webCompent.webView evaluateJavaScript:javaScriptStr completionHandler:nil];
});
}];
}
- (void)imButtonClick {
if (self.imUrl) {
[Phobos track:@"on_click_button" attributes:@{@"page_name" : self.pageName, @"button_name" : @"im_button" }];
[[GMRouter sharedInstance] pushScheme:self.imUrl];
}
}
- (void)shareFaceInfoImageMethod:(NSString *)imageDataStr {
_shareImage = [self stringToImageWith:imageDataStr imageStr:@"xcxImageString"];// 默认展示带有“小程序码”的海报
_ewmImage = [self stringToImageWith:imageDataStr imageStr:@"imageString"];// 二维码
if (!_shareImage) {
[self toast:@"糟糕,生成失败了,再试一次吧"];
return;
}
[self shareFaceInfo]; // 展示分享框
// 保存图片到相册
UIImageWriteToSavedPhotosAlbum(_shareImage, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);
[self toast:@"图片已存至相册,快分享给朋友炫耀一下"];
}
- (NSInteger)stringToImage:(NSString *)imageData name:(NSString *)name{
NSData *jsonData = [imageData dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:nil];
NSString *imageString = dict[name] ? : @"0";
return [imageString integerValue];
}
- (UIImage *)stringToImageWith:(NSString *)imageData imageStr:(NSString *)imageStr {
NSData *jsonData = [imageData dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:nil];
NSString *imageString = dict[imageStr] ?: @"";
UIImage *image = [UIImage imageWithData:[[NSData alloc] initWithBase64EncodedString:imageString options:NSDataBase64DecodingIgnoreUnknownCharacters]];
return image;
}
- (void)shareFaceInfo {
CGFloat scale1 = _shareImage.size.height / _shareImage.size.width;
CGFloat width1 = ShareImageHeight / scale1;
self.showImageView.image = self.shareImage;
self.showImageView.frame = CGRectMake((MAINSCREEN_WIDTH - width1)/2, OCNavigationBar.barHeight + 10, width1, ShareImageHeight);
[self.shareSheet addSubview:self.showImageView];
[self.shareSheet show];
[UIView animateWithDuration:0.5 animations:^{
_showImageView.alpha = 1.0;
}];
}
- (GMButton *)creatBtnWithImageName:(NSString *)imageStr title:(NSString *)title titleColor:(UIColor *)titleColor selector:(SEL)sel{
GMButton *btn = [[GMButton alloc] init];
btn.adjustsImageWhenHighlighted = NO;
btn.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 5);
btn.titleEdgeInsets = UIEdgeInsetsMake(0, 5, 0, 0);
[btn setBackgroundImage:[UIImage imageNamed:imageStr] forState:UIControlStateNormal];
[btn setTitle:title forState:UIControlStateNormal];
[btn setTitleColor:titleColor forState:UIControlStateNormal];
[btn.titleLabel setFont:[UIFont systemFontOfSize:14]];
[btn addTarget:self action:sel forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
return btn;
}
- (void)addSuspendBottomView {
CGFloat margin = (MAINSCREEN_WIDTH - 103 * 3) / 4;
// 专家咨询
GMButton *consultBtn = [self creatBtnWithImageName:@"face_photo_bgImage" title:@"专家咨询" titleColor:RGBCOLOR_HEX(0xFF5963) selector:@selector(imButtonClick)];
[consultBtn setImage:[UIImage imageNamed:@"face_test_consultIcon"] forState:UIControlStateNormal];
[self.bottomView addSubview:consultBtn];
[consultBtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(38);
make.width.mas_equalTo(103);
make.right.mas_equalTo(self.view.mas_right).offset(-margin);
make.bottom.mas_equalTo(self.view.mas_bottom).offset(btnBottomMargin);
}];
// 肤质分析
GMButton *skinAnalyseBtn = [self creatBtnWithImageName:@"face_analyse_bgImage" title:@"肤质分析" titleColor:UIColor.auxiliaryTextGreen selector:@selector(popViewToScanSkin)];
[skinAnalyseBtn setImage:[UIImage imageNamed:@"face_test_analyse"] forState:UIControlStateNormal];
[self.bottomView addSubview:skinAnalyseBtn];
[skinAnalyseBtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(38);
make.width.mas_equalTo(103);
make.centerX.mas_equalTo(self.view.mas_centerX);
make.bottom.mas_equalTo(self.view.mas_bottom).offset(btnBottomMargin);
}];
// 再测一次
GMButton *againBtn = [self creatBtnWithImageName:@"face_analyse_bgImage" title:@"再测一次" titleColor:UIColor.auxiliaryTextGreen selector:@selector(againBtnClick)];
[againBtn setImage:[UIImage imageNamed:@"face_test_photoIcon"] forState:UIControlStateNormal];
[self.bottomView addSubview:againBtn];
[againBtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(38);
make.width.mas_equalTo(103);
make.left.mas_equalTo(self.view.mas_left).offset(margin);
make.bottom.mas_equalTo(self.view.mas_bottom).offset(btnBottomMargin);
}];
}
-(UIView *)bottomView {
if (!_bottomView) {
_bottomView = [[UIView alloc] init];
_bottomView.backgroundColor = [UIColor clearColor];
[self.view insertSubview:_bottomView belowSubview:self.bgImageView];
[_bottomView mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(84 + UIView.safeAreaInsetsBottom);
make.left.right.mas_equalTo(0);
make.bottom.mas_equalTo(self.view.mas_bottom);
}];
// 添加背景颜色
GMImageView *bgImgView = [[GMImageView alloc] initWithImage:[UIImage imageNamed:@"face_bottomView_bgImage"]];
[_bottomView addSubview:bgImgView];
[bgImgView mas_updateConstraints:^(MASConstraintMaker *make) {
make.left.right.top.mas_equalTo(0);
make.height.mas_equalTo(84);
}];
// 安全区域防止透明
UIView *safeAreaView = [[UIView alloc] init];
safeAreaView.backgroundColor = [UIColor whiteColor];
[_bottomView addSubview:safeAreaView];
[safeAreaView mas_updateConstraints:^(MASConstraintMaker *make) {
make.left.right.bottom.mas_equalTo(0);
make.height.mas_equalTo(UIView.safeAreaInsetsBottom);
}];
}
return _bottomView;
}
- (ALShareSheet *)shareSheet {
if (!_shareSheet) {
__weak typeof(self) weakSelf = self;
_shareSheet = [[ALShareSheet alloc] init];
_shareSheet.bgAlphaColor = RGBCOLOR_HEX(0xF4F3F8);
_shareSheet.hiddenBlock = ^{ // 隐藏
[UIView animateWithDuration:0.25 animations:^{
weakSelf.showImageView.alpha = 0.0;
} completion:^(BOOL finished) {
[weakSelf.showImageView removeFromSuperview];
weakSelf.showImageView = nil;
}];
};
_shareSheet.shareTypeBlock = ^(GMSharePlatform platform) { // 分享
UIImage *image;
if (platform == GMSharePlatformWechatTimeline) {
image = weakSelf.shareImage;
}else{
image = weakSelf.ewmImage;
}
[[GMShareSDK shareInstance] shareImage:image platform:platform];
[Phobos track:@"page_click_share_channel" attributes:@{@"page_name":SafeString(weakSelf.pageName),
@"share_channel":SafeString([GMShareSDK shareChannel:platform])}];
};
}
return _shareSheet;
}
-(GMImageView *)showImageView{
if (!_showImageView) {
_showImageView = [[GMImageView alloc] init];
_showImageView.backgroundColor = UIColor.clearColor;
_showImageView.contentMode = UIViewContentModeScaleAspectFit;
_showImageView.layer.cornerRadius = 10;
_showImageView.layer.masksToBounds = YES;
}
return _showImageView;
}
#pragma mark -其它逻辑处理
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo {}
// 返回按钮点击事件
-(void)backBtnClick {
BOOL isPop = YES;
for (UIViewController *vc in AppDelegate.navigation.childViewControllers) {
if ([vc isKindOfClass:[NewFaceShotController class]]){
isPop = NO;
[AppDelegate.navigation popToViewController:vc animated:YES];
return;
}
}
if (isPop) {
[AppDelegate.navigation popToRootViewControllerAnimated:YES];
}
}
#pragma mark -添加背景imageView
-(GMImageView *)bgImageView{
if (!_bgImageView) {
_bgImageView = [[GMImageView alloc] initWithImage:[UIImage imageNamed:@"facev5_scan_share_bgImage"]];
_bgImageView.frame = CGRectMake(0, 0, MAINSCREEN_WIDTH, MAINSCREEN_HEIGHT);
_bgImageView.contentMode = UIViewContentModeScaleAspectFill;
UIImageView *imageV = [[GMImageView alloc] initWithImage:[UIImage imageNamed:@"facev5_scan_share_image"]];
imageV.userInteractionEnabled = NO;
CGFloat w = MAINSCREEN_WIDTH - 25;
CGFloat h = 479 * w / 350;
imageV.frame = CGRectMake(0, 0, w, h);
imageV.center = _bgImageView.center;
[_bgImageView addSubview:imageV];
}
return _bgImageView;
}
-(GMImageView *)bgNograyImageView {
if (!_bgNograyImageView) {
_bgNograyImageView = [[GMImageView alloc] initWithImage:[UIImage imageNamed:@"fave4_share_bgImage"]];
_bgNograyImageView.frame = CGRectMake(0, 0, MAINSCREEN_WIDTH, MAINSCREEN_HEIGHT);
_bgNograyImageView.contentMode = UIViewContentModeScaleAspectFill;
}
return _bgNograyImageView;
}
// 右侧按钮点击事件
- (void)rightButtonClicked:(OCNavigationBarButton *)button {
[self shareFriend];
}
// 返回到首页
-(void)goHomeBtnClick {
[Phobos track:@"on_click_button" attributes:@{@"page_name" :SafeString(self.pageName), @"button_name" : @"back_home"}];
[self.navigationController popToRootViewControllerAnimated:YES];
}
// 再测一次
-(void)againBtnClick {
[Phobos track:@"on_click_button" attributes:@{@"page_name":SafeString(self.pageName),
@"button_name":@"scan_again"}];
[self backBtnClick];
}
// 返回到测肤页面
-(void)popViewToScanSkin{
[Phobos track:@"on_click_button" attributes:@{@"page_name" :SafeString(self.pageName), @"button_name" : @"goto_face_detect"}];
[[NSNotificationCenter defaultCenter] postNotificationName:@"GMFaceInfoSharePopToFaceSkin" object:nil];
[self backBtnClick];
}
// 分享给朋友
-(void)shareFriend {
[Phobos track:@"page_click_share" attributes:@{@"page_name" : SafeString(self.pageName)}];
[self.webCompent.webView evaluateJavaScript:@"window.gm.pack.run('share')" completionHandler:nil];
}
// leftButton点击事件
-(void)backAction:(OCNavigationBarButton *)button{
if (self.webLoadSucceed){
[self.webCompent.webView evaluateJavaScript:@"window.gm.pack.run('signBack')" completionHandler:nil];
}else{
[self backBtnClick];
}
}
#pragma mark WKWebView 代理方法
-(void)jsControlBackToMethod:(NSString *)str{
if([str isEqualToString:@"pop"]){ // 返回测肤页面
[self backBtnClick];
}else if([str isEqualToString:@"skin"]){ // 返回测脸页面
[[NSNotificationCenter defaultCenter] postNotificationName:@"GMFaceInfoSharePopToFaceSkin" object:nil];
[self backBtnClick];
}
}
// 代理方法回掉, 删掉的话, 进入落地页会闪退
-(void)globalDataLoaded:(NSDictionary *)data{
self.webLoadSucceed = YES;
}
// 判断按钮滑动的位置
-(void)jsPageReachBottom:(BOOL)isScrollbottom{}
- (void)consultationUrl:(NSDictionary *)data {
if ([data[@"url"] isNonEmpty]) {
self.imUrl = data[@"url"];
}
}
-(void)closeFaceLoading {
self.bgImageView.hidden = YES;
self.navigationBar.rightButton.hidden = NO;
if (isGray) { // 灰度
self.bgImageView.hidden = YES;
self.navigationBar.backgroundColor = UIColor.whiteColor;
}else { // 非灰度
self.bgNograyImageView.hidden = YES;
}
[self addSuspendBottomView]; // 添加悬浮按钮
}
-(void)controlTitleBarVisible:(BOOL)show{
if (isGray) {return;}
if (show) {
self.navigationBar.backgroundColor = RGBCOLOR_HEX(0xF5B5E3);
}else{
self.navigationBar.backgroundColor = UIColor.clearColor;
}
}
#pragma mark 暂时不知道这几个方法的作用
- (void)WKWebViewScroll:(WKWebView *)webView CaptureCompletionHandler:(void(^)(UIImage *capturedImage))completionHandler {
// 制作了一个UIView的副本
UIView *snapShotView = [webView snapshotViewAfterScreenUpdates:YES];
snapShotView.frame = CGRectMake(webView.frame.origin.x, webView.frame.origin.y, snapShotView.frame.size.width, snapShotView.frame.size.height);
[webView.superview addSubview:snapShotView];
// 获取当前UIView可滚动的内容长度
CGPoint scrollOffset = webView.scrollView.contentOffset;
// 向上取整数 - 可滚动长度与UIView本身屏幕边界坐标相差倍数
float maxIndex = ceilf(webView.scrollView.contentSize.height/webView.bounds.size.height);
// 保持清晰度
UIGraphicsBeginImageContextWithOptions(webView.scrollView.contentSize, false, [UIScreen mainScreen].scale);
// 滚动截图
[self ZTContentScroll:webView PageDraw:0 maxIndex:(int)maxIndex drawCallback:^{
UIImage *capturedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// 恢复原UIView
[webView.scrollView setContentOffset:scrollOffset animated:NO];
[snapShotView removeFromSuperview];
// UIImage *resultImg = [UIImage imageWithData:UIImageJPEGRepresentation(capturedImage, 0.5)];
completionHandler(capturedImage);
}];
}
// 滚动截图
- (void)ZTContentScroll:(WKWebView *)webView PageDraw:(int)index maxIndex:(int)maxIndex drawCallback:(void(^)(void))drawCallback{
[webView.scrollView setContentOffset:CGPointMake(0, (float)index * webView.frame.size.height)];
CGRect splitFrame = CGRectMake(0, (float)index * webView.frame.size.height, webView.bounds.size.width, webView.bounds.size.height);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[webView drawViewHierarchyInRect:splitFrame afterScreenUpdates:YES];
if(index < maxIndex){
[self ZTContentScroll:webView PageDraw: index + 1 maxIndex:maxIndex drawCallback:drawCallback];
}else{
drawCallback();
}
});
}
- (void)showNativeEmptyView {
[self showEmptyView:GMEmptyViewTypeException];
}
- (void)emptyViewDidClickReload {
[self hideEmptyView];
[self.webCompent reload];
}
@end
//
// GMFacePointViewController.h
// Gengmei
//
// Created by 刘鹿杰 on 2019/6/26.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@class GMFaceV3Model;
@interface GMFacePointViewController : WMBaseViewController
@property(nonatomic, assign) BOOL isGene; // 区分是不是从面孔起源过来的
- (instancetype)initWithModel:(GMFaceV3Model *)faceModel;
@end
NS_ASSUME_NONNULL_END
//
// GMFacePointViewController.m
// Gengmei
//
// Created by 刘鹿杰 on 2019/6/26.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMFacePointViewController.h"
#import "GMFaceInfoShareViewController.h"
#import "Gengmei-Swift.h"
#import "UIView+GMExtension.h"
#import "UIImage+GMExtension.h"
#import "GMImageView+GMFace.h"
#import "GMDrawDottedView.h"
#import "GMMarkView.h"
#import "GMTitleView.h"
#import "ALFacePointsModel.h"
#import "GMFaceV3Model.h"
#import "ALTransFormFaceUtil.h"
#import "GMFaceV3Util.h"
#import "GMGeneMemoryResultViewController.h"
#import "NSString+jsonData.h"
#import "Lottie.h"
#import "UIImage+WebP.h"
#define AnimationDuration 0.75 // 动画执行时间
#define AnimationDuration1 1.0 // 动画执行时间为1.0 秒
#define Blue_line [UIColor colorWithHexString:@"0x4EC4CD" alpha:0.6]
#define TitleToTopHight [OCNavigationBar barHeight] + 101
@interface UIView (Transform)
@end
@implementation UIView (Transform)
- (void)setAnchorPoint:(CGPoint)anchorPoint {
CGFloat x = anchorPoint.x/self.bounds.size.width;
CGFloat y = anchorPoint.y/self.bounds.size.height;
CGPoint oldOrigin = self.frame.origin;
self.layer.anchorPoint = CGPointMake(x, y);
CGPoint newOrigin = self.frame.origin;
CGPoint transition;
transition.x = newOrigin.x - oldOrigin.x;
transition.y = newOrigin.y - oldOrigin.y;
self.center = CGPointMake (self.center.x - transition.x, self.center.y - transition.y);
}
@end
@interface GMFacePointViewController ()
@property (nonatomic, strong) GMButton *backBtn; // 返回按钮
@property (nonatomic, strong) GMButton *ignoreBtn; // 跳过按钮
@property (nonatomic, strong) GMImageView *titleImageView; // 页面标题imageView
@property (nonatomic, strong) GMImageView *picImageView; // 当前页面图片
@property (nonatomic, assign) BOOL stopGCD; // 是否停用GCD, 结束当前页面动画
@property (nonatomic, strong) GMFaceV3Model *faceModel;
@property (nonatomic, strong) NSMutableArray *allViewLine; // 页面需要消失的线
@property (nonatomic, strong) NSMutableArray *viewshowTime; // 每个页面需要延时的时间
@property (nonatomic, strong) UIImageView *placeholderImage;
//@property (nonatomic, strong) UIImageView *finallyImageView;
@property (nonatomic, assign) BOOL isShowAnimate;
@property (nonatomic, strong) SDAnimatedImageView *geneEndImageView; // 面孔起源动画结尾
@property (nonatomic, strong) GMImageView *geneEndBgImageView; // 面孔起源动画结尾
@end
@implementation GMFacePointViewController
- (instancetype)initWithModel:(GMFaceV3Model *)faceModel {
if (self = [super init]) {
self.faceModel = [ALTransFormFaceUtil zoomFaceImagelScreenWidth:faceModel];
}
return self;
}
- (void)initController {
[super initController];
self.pageName = @"face_scan_loading";
}
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationBar.hidden = YES;
self.fd_interactivePopDisabled = YES;
self.view.backgroundColor = [UIColor colorWithHexString:@"666666"];
[self setupPlaceHoldImageV];
[self animateFaceImageToDesignatedLocation];
self.extraParam = [NSString jsonStringWithObject:@{@"land_tab_name" : self.isGene ? @"AI面孔" : @"AI测脸"}];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:YES];
if (self.geneEndImageView) {
[self.geneEndImageView stopAnimating];
[self.geneEndBgImageView removeFromSuperview];
}
}
- (void)animateFaceImageToDesignatedLocation {
// 1 standardPoint:标准点。同时画一个standardView作为参考
CGPoint standardPoint = CGPointMake(MAINSCREEN_WIDTH / 2, MAINSCREEN_HEIGHT * templeTopScale);
// 2 faceCenterPoint:人脸中心点。同时画一个绿色faceCenterView作为参考。
// faceCenterView 是被添加到图片上的,以便验证后续的平移、旋转、缩放是正确的
CGPoint faceCenterPoint = CGPointMake((self.faceModel.templeInfo.leftX + self.faceModel.templeInfo.rightX)/2, (self.faceModel.templeInfo.leftY + self.faceModel.templeInfo.rightY)/2);
// 3、将人脸中心点移动到标准点,所以要先计算平移的 x和y
CGFloat translateX = standardPoint.x - faceCenterPoint.x;
CGFloat translateY = standardPoint.y - faceCenterPoint.y;
// 3.1 计算平移后的 frame
CGRect newRect = _placeholderImage.frame;
newRect.origin.x += translateX;
newRect.origin.y += translateY;
// 5 接下来要旋转、缩放,所以要保证图片此时的 anchorPoint、人脸中心点、标准参考点,三点重叠
[_placeholderImage setAnchorPoint:faceCenterPoint];
// 6 旋转transform
CGAffineTransform roateTransform = [ALTransFormFaceUtil rollTransformImageV:self.faceModel];
// 7 缩放transform
// 7.1 由两个太阳穴的点可以得到脸的宽度
CGFloat betweenX = self.faceModel.templeInfo.leftX - self.faceModel.templeInfo.rightX;
CGFloat betweenY = self.faceModel.templeInfo.leftY - self.faceModel.templeInfo.rightY;
CGFloat faceWidth = sqrt(betweenX*betweenX + betweenY*betweenY);
// 7.2 标准宽度与脸宽度对比得到缩放因子,进而得到transform
CGFloat standardWidth = MAINSCREEN_WIDTH * templeWidthScale;
CGFloat imageScale = standardWidth / faceWidth;
CGAffineTransform lastTransform = CGAffineTransformScale(roateTransform, imageScale, imageScale);
[UIView animateWithDuration:0.5 animations:^{
// 4 开始平移
_placeholderImage.frame = newRect;
}];
[UIView animateWithDuration:0.5 delay:0.5 options:UIViewAnimationOptionCurveEaseInOut animations:^{
// 8 应用最终的 transform
_placeholderImage.transform = lastTransform;
} completion:^(BOOL finished) {
dispatch_async(dispatch_get_main_queue(), ^{
self.faceModel = [ALTransFormFaceUtil mapFaceModelToScreencCoordinates:self.faceModel aroundPoint:standardPoint roate:roateTransform scale:imageScale translate:CGPointMake(translateX, translateY)];
self.faceModel.image = nil; // 存储模型到沙盒前, 需要清空image对象
[GMCache storeObjectAtDocumentPathWithkey:@"GMFaceV3Model" object:self.faceModel.toDictionary];
self.faceModel.image = [self snapScreenImageMethod];
NSData *imageData = UIImagePNGRepresentation(self.faceModel.image);
[GMCache storeObjectAtDocumentPathWithkey:@"GMFaceV3ModelImage" object:imageData];
// 控制动画效果
[self setupSubView];
[self setNavigationBarUI];
});
}];
}
- (void)snapFaceMethod {
CGRect r1 = CGRectMake(self.faceModel.rect.left, self.faceModel.rect.top, self.faceModel.rect.right - self.faceModel.rect.left, self.faceModel.rect.bottom - self.faceModel.rect.top);
//对r1 放大1.8倍 取出来传给 h5 显示头像使用
CGFloat scale = 1.6;
CGFloat newRectX = r1.origin.x - ((scale - 1) / 2 * r1.size.width);
CGFloat newRectY = r1.origin.y - ((scale - 1) * r1.size.height);
CGRect newRect = CGRectMake(newRectX, newRectY, r1.size.width*scale, r1.size.height*scale);
UIImage *h5Image = [self imageFromView:self.view atFrame:newRect];
if (self.isGene) { // 面孔起源
[self uploadImageToQiniu:h5Image key:@"researchUserImg"];
} else {
[self uploadImageToQiniu:h5Image key:@"scanUserImg"];
}
}
// 存取base64 给h5
- (void)uploadImageToQiniu:(UIImage *)image key:(NSString *)key {
[GMFaceV3Util uploadImageToQiNiu:image successBlock:^(id responseObject) {
NSString *path = (NSString *)responseObject;
//由于是一个段路径 所以要拼接上域名 https://pic.igengmei.com/ 以及后缀给h5 -aspectscale800
NSString *urlStr = [NSString stringWithFormat:@"https://pic.igengmei.com/%@-aspectscale800", path];
[GMCache storeObjectAtDocumentPathWithkey:key object:urlStr];
}];
}
#pragma mark -- 获得某个范围内的屏幕图像(截图)
- (UIImage *)imageFromView:(UIView *)theView atFrame:(CGRect)atFrame {
// 下面方法,第一个参数表示区域大小。第二个参数表示是否是非透明的。如果需要显示半透明效果,需要传NO,否则传YES。第三个参数就是屏幕密度了,关键就是第三个参数 [UIScreen mainScreen].scale。
UIGraphicsBeginImageContextWithOptions(theView.frame.size, NO, 1.0);
[theView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
CGRect myImageRect = atFrame;
CGImageRef imageRef = image.CGImage;
CGImageRef subImageRef = CGImageCreateWithImageInRect(imageRef,myImageRect );
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawImage(context, myImageRect, subImageRef);
UIImage* smallImage = [UIImage imageWithCGImage:subImageRef];
CGImageRelease(subImageRef);
UIGraphicsEndImageContext();
return smallImage;
}
- (void)setupPlaceHoldImageV {
_placeholderImage = [[UIImageView alloc] initWithFrame:CGRectMake(self.faceModel.imageX, self.faceModel.imageY, self.faceModel.imageW, self.faceModel.imageH)];
_placeholderImage.contentMode = UIViewContentModeScaleAspectFit;
_placeholderImage.image = self.faceModel.image;
[self.view addSubview:_placeholderImage];
[self snapFaceMethod];
}
- (void)setAnchorPoint:(CGPoint)anchorPoint forView:(UIView *)view {
CGPoint oldOrigin = view.frame.origin;
view.layer.anchorPoint = anchorPoint;
CGPoint newOrigin = view.frame.origin;
CGPoint transition;
transition.x = newOrigin.x - oldOrigin.x;
transition.y = newOrigin.y - oldOrigin.y;
view.center = CGPointMake (view.center.x - transition.x, view.center.y - transition.y);
}
- (void)drawCircleAtPoint:(CGPoint)point color:(UIColor *)color {
CAShapeLayer *solidLine = [CAShapeLayer layer];
CGMutablePathRef solidPath = CGPathCreateMutable();
solidLine.lineWidth = 2.0f ;
solidLine.strokeColor = color.CGColor;
solidLine.fillColor = [UIColor clearColor].CGColor;
CGPathAddEllipseInRect(solidPath, nil, CGRectMake(point.x, point.y, 4, 4));
solidLine.path = solidPath;
CGPathRelease(solidPath);
[self.view.layer addSublayer:solidLine];
}
#pragma mark - 获得屏幕截图
- (UIImage *)snapScreenImageMethod {
UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, YES, [[UIScreen mainScreen] scale]);
if ([self respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) {
[self.view drawViewHierarchyInRect:self.view.bounds afterScreenUpdates:NO];
} else {
CGContextRef context = UIGraphicsGetCurrentContext();
[self.view.layer renderInContext:context];
}
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return viewImage;
}
- (void)setupSubView {
[self addImageView]; // 设置ImageView
[self addMaskView]; // 添加遮罩层
[self addTitleImageView:@"facev3_analyse"]; // 添加面部分析图片
[self controlAnimation]; // 控制动画
}
- (void)setNavigationBarUI {
[self.view addSubview:self.backBtn];
[self.backBtn mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(20);
make.centerY.equalTo(self.view.mas_top).offset([OCNavigationBar navigationItemCenterY]);
}];
[self.view addSubview:self.ignoreBtn];
[self.ignoreBtn mas_remakeConstraints:^(MASConstraintMaker *make) {
make.right.mas_equalTo(-20);
make.centerY.equalTo(self.backBtn.mas_centerY);
}];
}
// 添加picImageView
- (void)addImageView {
self.picImageView = [[GMImageView alloc] initWithImage:self.faceModel.image];
_picImageView.contentMode = UIViewContentModeScaleAspectFill;
_picImageView.frame = self.view.frame;
[_placeholderImage removeFromSuperview];
[self.view addSubview:_picImageView];
}
// 添加遮罩层
- (void)addMaskView {
GMMarkView * markView = [[GMMarkView alloc] init];
markView.backgroundColor = [UIColor colorWithHexString:@"000000" alpha:0.25];
markView.alpha = 0;
markView.frame = CGRectMake(0, 0, MAINSCREEN_WIDTH, MAINSCREEN_HEIGHT);
[self.view addSubview:markView];
[UIView animateWithDuration:1.0 animations:^{
markView.alpha = 1;
}];
}
#pragma mark 使用递归方法 - 串行控制动画效果
// 控制动画效果
- (void)controlAnimation {
self.viewshowTime = [NSMutableArray arrayWithArray:@[@(2.25),@(2.25),@(3),@(3.25)]];
__weak typeof(self) weakSelf = self; // 展示面部分析动画后, 停留0.75s,动画耗时0.75s 共用 1.5s
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (weakSelf.stopGCD) { return ;}
[weakSelf drawOneViewAnimation]; // 共耗时 1.25s
[weakSelf controlVcAnimationWithIndex:2 Delay:2.25];
});
}
- (void)controlVcAnimationWithIndex:(NSInteger)index Delay:(CGFloat)delayTime{
__weak typeof(self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (weakSelf.stopGCD) { return ;}
[weakSelf clearFristViewLineView]; // 耗时0.75s, 等待0.75s
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (weakSelf.stopGCD) { return ;}
if (index == 2) { // 开启第二个页面的动画
[weakSelf drawSectionViewAnimation];
}else if (index == 3){ // 开启第三页面的动画
[weakSelf drawThirdViewAnimation];
}else if (index == 4){ // 开启第四页面的动画
[weakSelf drawFourViewAnimation];
[weakSelf controlVcAnimationFive]; // 开启第五页面的动画
}
if (index < 4) {
[weakSelf controlVcAnimationWithIndex:index + 1 Delay:[weakSelf.viewshowTime[index - 1] floatValue]];
}
});
});
}
// 控制第五个页面的动画
- (void)controlVcAnimationFive {
__weak typeof(self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (weakSelf.stopGCD) { return ;}
[weakSelf clearFristViewLineView]; // 耗时0.75s
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(AnimationDuration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (weakSelf.stopGCD) { return ;}
[weakSelf addTitleImageView:@"facev3_eye_analyse"]; // 0.75s
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(AnimationDuration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (weakSelf.stopGCD) { return ;}
[weakSelf drawFiveViewAnimation]; // 3.75s
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4.75 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (weakSelf.stopGCD) { return ;}
[weakSelf controlVcAnimationSix];
});
});
});
});
}
// 控制第六个页面的动画
- (void)controlVcAnimationSix {
__weak typeof(self) weakSelf = self;
[weakSelf clearFristViewLineView]; // 清除页面线条
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.75 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (weakSelf.stopGCD) { return ;}
[weakSelf updatePicImageViewframeP]; // 0.75s
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.75 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (weakSelf.stopGCD) { return ;}
// 在这里开启新的动画
[weakSelf drawAddNewAnimation];
[weakSelf.allViewLine addObject:weakSelf.titleImageView];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.75 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (weakSelf.stopGCD) { return ;}
[weakSelf clearFristViewLineView]; // 清除页面线条
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.75 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (weakSelf.stopGCD) { return ;}
[weakSelf addTitleImageView:@"facev3_mouth_analyse"]; // 0.75s
[weakSelf.allViewLine addObject:weakSelf.titleImageView];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.75 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (weakSelf.stopGCD) { return ;}
[weakSelf drawSixViewAnimation]; // 1.75s
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.75 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (weakSelf.stopGCD) { return ;}
if (weakSelf.isGene) {
[weakSelf drawGeneMemoryEndAnimation]; // 0.23s
} else {
[weakSelf ignoreMethod]; // 跳转页面
}
});
});
});
});
});
});
}
#pragma mark 第一个页面所有动画
- (void)drawOneViewAnimation {
NSArray *keyPointModelArray = self.faceModel.analysis.santingInfo.realLinePoints;
NSArray *subTitle = self.faceModel.analysis.santingInfo.subTitle;
// 线宽
NSArray *array1 = @[@(0.75), @(0.6), @(0.45)];
if (keyPointModelArray.count != 3) {return;}
// 画三条横线
[keyPointModelArray enumerateObjectsUsingBlock:^(NSArray * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
GMFaceCommonPositionModel *pointModel = (GMFaceCommonPositionModel *)obj;
GMImageView *lineImage = [GMImageView creatHorizontalPathWithY:pointModel.y];
lineImage.centerX = self.view.centerX;
[self.view addSubview:lineImage];
[UIView animateWithDuration:AnimationDuration animations:^{
lineImage.width = [array1[idx] floatValue] * MAINSCREEN_WIDTH;
lineImage.centerX = self.view.centerX;
}];
[self.allViewLine addObject:lineImage];
}];
// 画中间两条描述竖线
NSArray *contentModelArray = [self.faceModel.analysis.santingInfo.content copy];
for (int i = 1; i < keyPointModelArray.count; i++) {
GMFaceCommonContentModel *contentModel = contentModelArray[i - 1];
NSString *dect = [NSString stringWithFormat:@"%@\n%@",contentModel.title, contentModel.value];
CGSize dectSize = [dect sizeWithAttributes:@{NSFontAttributeName:[UIFont gmFont:9]}];
CGPointModel *pointModel0 = keyPointModelArray[i - 1];
CGPointModel *pointModel = keyPointModelArray[i];
GMDrawDottedView *drawHView = [[GMDrawDottedView alloc] initWithLineType:drawViewTypeVerticalDotted addlabel:wordPlaceCenter addDect:dect];
drawHView.backgroundColor = [UIColor clearColor];
drawHView.frame = CGRectMake(MAINSCREEN_WIDTH - (60 + i * dectSize.width), pointModel0.y + 1, dectSize.width, pointModel.y - pointModel0.y - 2);
drawHView.alpha = 0;
[self.view addSubview:drawHView];
[self.allViewLine addObject:drawHView];
[UIView animateWithDuration:AnimationDuration1 animations:^{
drawHView.alpha = 1;
}];
}
// 添加描述文字
[self leftPopTitleWith:self.allViewLine AddSubtitle:subTitle Animation:0.5];
}
#pragma mark 第二个页面所有动画
- (void)drawSectionViewAnimation {
NSArray *wuyanPointArray = self.faceModel.analysis.wuyanInfo.realLinePoints;
NSArray *santingArray = self.faceModel.analysis.santingInfo.realLinePoints; // 从三庭页面获取P点
NSArray *subTitle = self.faceModel.analysis.wuyanInfo.subTitle;
GMFaceCommonPositionModel *pModel = santingArray[1];
if (wuyanPointArray.count != 6) {return;}
[wuyanPointArray enumerateObjectsUsingBlock:^(NSArray * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
CGPointModel *pointModel = (CGPointModel *)obj;
GMImageView *lineImage = [GMImageView creatVerticalPathWithX:pointModel.x];
lineImage.centerY = pModel.y;
[self.view addSubview:lineImage];
[UIView animateWithDuration:AnimationDuration animations:^{
lineImage.height = 300;
lineImage.centerY = pModel.y;
}];
// 保存数据
[self.allViewLine addObject:lineImage];
}];
// 计算横着的带箭头的虚线
NSArray *triangleSubTitle = self.faceModel.analysis.goldTriangleInfo.pointsTriangle;
GMFaceCommonPositionModel *pModel1 = triangleSubTitle[0];
NSArray *yArray = @[@(pModel1.y + 90), @(pModel1.y + 30), @(pModel1.y + 60), @(pModel1.y + 30),@(pModel1.y + 90)];
NSArray *contentModelArray = self.faceModel.analysis.wuyanInfo.content;
for (int i = 1; i < wuyanPointArray.count; i++) {
GMFaceCommonContentModel *contentModel = contentModelArray[i - 1];
NSString *dect = [NSString stringWithFormat:@"%@\n%@",contentModel.title, contentModel.value];
CGSize dectSize = [dect sizeWithAttributes:@{NSFontAttributeName:[UIFont gmFont:9]}];
CGPointModel *pointModel0 = wuyanPointArray[i - 1];
CGPointModel *pointModel = wuyanPointArray[i];
// 文字在下面
GMDrawDottedView *drawHView = [[GMDrawDottedView alloc] initWithLineType:drawViewTypeHorizontalDotted addlabel:wordPlaceBottom addDect:dect];
drawHView.backgroundColor = [UIColor clearColor];
drawHView.frame = CGRectMake(pointModel0.x, [yArray[i - 1] floatValue], pointModel.x - pointModel0.x, dectSize.height + 10);
drawHView.alpha = 0;
[UIView animateWithDuration:AnimationDuration animations:^{
drawHView.alpha = 1;
}];
[self.view addSubview:drawHView];
[self.allViewLine addObject:drawHView];
}
// 添加中间两条横线
NSArray *dottedPointArray = [self.faceModel.analysis.wuyanInfo.dottedPoints copy];
for (int i = 0; i< 2; i++) {
GMFaceCommonPositionModel *contentModel = dottedPointArray[i];
GMImageView *lineImage1 = [GMImageView creatHorizontalDottedWithY:contentModel.y];
lineImage1.centerX = self.view.centerX;
lineImage1.alpha = 0;
[UIView animateWithDuration:AnimationDuration1 animations:^{
lineImage1.width = MAINSCREEN_WIDTH;
lineImage1.alpha = 1;
lineImage1.centerX = self.view.centerX;
}];
[self.view addSubview:lineImage1];
[self.allViewLine addObject:lineImage1];
}
// 添加描述文字
[self leftPopTitleWith:self.allViewLine AddSubtitle:subTitle Animation:0.5];
}
#pragma mark 第三个页面所有动画
- (void)drawThirdViewAnimation {
NSArray *pointsLeft = self.faceModel.analysis.featureInfo.pointsLeft; // 添加画脸的线
NSArray *pointsRight = self.faceModel.analysis.featureInfo.pointsRight;
NSArray *subTitle = self.faceModel.analysis.featureInfo.subTitle; // 添加描述文字
NSArray *subTitle1 = self.faceModel.analysis.niebuquankuanInfo.subTitle;
NSArray *niebuPoints = self.faceModel.analysis.niebuquankuanInfo.niebuPoints; // 添加画脸的线
NSArray *quankuanPoints = self.faceModel.analysis.niebuquankuanInfo.quankuanPoints;
NSArray *nbqkContentArray = self.faceModel.analysis.niebuquankuanInfo.content;
// 画脸型折线条
CALayer *layer = [self drawLineMethod:pointsLeft Color:Blue_line];
[self.allViewLine addObject:layer];
CALayer *layer1 = [self drawLineMethod:pointsRight Color:Blue_line];
[self.allViewLine addObject:layer1];
// 画竖线
__weak typeof (self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(AnimationDuration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (weakSelf.stopGCD) { return ;}
NSMutableArray *verticalLineArray = [NSMutableArray arrayWithObjects:pointsLeft[0],pointsLeft[1], nil];
[verticalLineArray addObject:pointsRight.firstObject];
[verticalLineArray addObject:pointsRight[1]];
[verticalLineArray enumerateObjectsUsingBlock:^(NSArray * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
GMFaceCommonPositionModel *pointModel = (GMFaceCommonPositionModel *)obj;
GMImageView *lineImage = [GMImageView creatVerticalPathWithX:pointModel.x];
lineImage.centerY = pointModel.y;
[weakSelf.view addSubview:lineImage];
[UIView animateWithDuration:AnimationDuration animations:^{
lineImage.height = 70;
lineImage.centerY = pointModel.y;
}];
// 保存数据
[weakSelf.allViewLine addObject:lineImage];
}];
// 获取中心点坐标
GMFaceCommonPositionModel *niebuCenterModel = [self calculateCenterModelWithArray:niebuPoints];
GMFaceCommonPositionModel *quankuanCenterModel = [self calculateCenterModelWithArray:quankuanPoints];
// 添加颞部新增横线
NSArray<GMFaceCommonPositionModel *> *hArrry = @[niebuCenterModel,quankuanCenterModel];
NSArray *widthArray = @[@(284),@(284)];
[self drawSixhLine:hArrry widthArray:widthArray];
// 展示比例
GMFaceCommonContentModel *nbqkModel = nbqkContentArray[0];
[self creatVerticalDottedLineWith:nbqkModel addArray:@[niebuCenterModel,quankuanCenterModel] offset:30];
});
// 颧弓宽度
NSArray *contentArray = self.faceModel.analysis.featureInfo.content;
GMFaceCommonPositionModel* leftPointModel0 = pointsLeft[0];
GMFaceCommonPositionModel* leftPointModel1 = pointsLeft[1];
GMFaceCommonPositionModel* rightPointModel0 = pointsRight[0];
GMFaceCommonPositionModel* rightPointModel1 = pointsRight[1];
GMFaceCommonContentModel *qgModel = contentArray[0];
NSString *qgString = [NSString stringWithFormat:@"%@%@",qgModel.title,qgModel.value];
CGSize qgSize = [qgString sizeWithAttributes:@{NSFontAttributeName:[UIFont gmFont:9]}];
GMDrawDottedView *racDottedLine = [[GMDrawDottedView alloc] initWithLineType:drawViewTypeHorizontalDotted addlabel:wordPlaceCenter addDect:qgString];
racDottedLine.alpha = 0.0;
racDottedLine.backgroundColor = UIColor.clearColor;
racDottedLine.frame = CGRectMake(leftPointModel0.x, leftPointModel0.y + 10, rightPointModel0.x - leftPointModel0.x, qgSize.height + 5);
[self.view addSubview:racDottedLine];
[self.allViewLine addObject:racDottedLine];
// 下颌角宽度
GMFaceCommonContentModel *xhjModel = contentArray[1];
NSString *xhjString = [NSString stringWithFormat:@"%@%@",xhjModel.title,xhjModel.value];
CGSize xhjSize = [xhjString sizeWithAttributes:@{NSFontAttributeName:[UIFont gmFont:9]}];
GMDrawDottedView *xhjDottedLine = [[GMDrawDottedView alloc] initWithLineType:drawViewTypeHorizontalDotted addlabel:wordPlaceCenter addDect:xhjString];
xhjDottedLine.alpha = 0.0;
xhjDottedLine.backgroundColor = UIColor.clearColor;
xhjDottedLine.frame = CGRectMake(leftPointModel1.x, leftPointModel1.y, rightPointModel1.x - leftPointModel1.x, xhjSize.height + 5);
[self.view addSubview:xhjDottedLine];
[self.allViewLine addObject:xhjDottedLine];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(AnimationDuration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (weakSelf.stopGCD) { return ;}
[UIView animateWithDuration:AnimationDuration1 animations:^{
racDottedLine.alpha = 1;
xhjDottedLine.alpha = 1;
}];
});
// 添加描述文字
NSMutableArray *allsubTitle = [NSMutableArray arrayWithArray:subTitle];
[allsubTitle addObjectsFromArray:subTitle1];
[self leftPopTitleWith:self.allViewLine AddSubtitle:allsubTitle Animation:1.25];
}
// 计算两点之间的水平宽度
-(CGFloat)calculateWidthWithArray:(NSArray*)array{
GMFaceCommonPositionModel *model0 = array[0];
GMFaceCommonPositionModel *model1 = array[1];
return model1.x - model0.x;
}
// 返回中心点model
-(GMFaceCommonPositionModel *)calculateCenterModelWithArray:(NSArray*)array{
GMFaceCommonPositionModel *centerModel = [GMFaceCommonPositionModel new];
GMFaceCommonPositionModel *model0 = array[0];
GMFaceCommonPositionModel *model1 = array[1];
centerModel.x = (model0.x + model1.x) / 2;
centerModel.y = (model0.y + model1.y) / 2;
return centerModel;
}
#pragma mark 第四个页面所有动画
- (void)drawFourViewAnimation {
NSArray *triangleArray = self.faceModel.analysis.goldTriangleInfo.pointsTriangle; // 获取黄金三角三个点
NSArray *mouthArray = self.faceModel.analysis.goldTriangleInfo.pointsMouth; // 获取嘴角两个点
NSArray *subTitle = self.faceModel.analysis.goldTriangleInfo.subTitle; // 添加描述文字
NSArray *chinArray = self.faceModel.analysis.goldTriangleInfo.pointsChin; // 获取下巴的三个点
NSArray *contentArray = self.faceModel.analysis.goldTriangleInfo.content; // 获取所有角度
// 画下巴线条
GMFaceCommonPositionModel *point0 = chinArray[1]; // 中心点
GMFaceCommonPositionModel *point1 = chinArray.firstObject; // 左侧点
GMFaceCommonPositionModel *point2 = chinArray.lastObject; // 右侧点
// 延长线条
CGPoint leftEndPoint = CGPointMake(point0.x - 2*(point0.x - point1.x), point0.y - 2*( point0.y - point1.y));
CGPoint rightEndPoint = CGPointMake(point0.x + 2*(point2.x-point0.x), point0.y - 2*( point0.y - point2.y));
GMFaceCommonPositionModel *leftModel = [GMFaceCommonPositionModel creatFaceCommonModelWithCGPoint:leftEndPoint];
GMFaceCommonPositionModel *rightModel = [GMFaceCommonPositionModel creatFaceCommonModelWithCGPoint:rightEndPoint];
// 添加下巴两天线
CALayer *layer1 = [self drawLineMethod:@[point0,leftModel] Color:Blue_line];
CALayer *layer2 = [self drawLineMethod:@[point0,rightModel] Color:Blue_line];
[self.allViewLine addObject:layer1];
[self.allViewLine addObject:layer2];
// 添加下巴角度
GMFaceCommonContentModel *lastContentModel = contentArray.lastObject;
GMLabel *dectLabel1 = [self creatLabelWithText:lastContentModel.value Font:9 Color:UIColor.whiteColor Size:CGRectMake(point0.x - 30, point0.y - 20, 60, 15)];
[self.allViewLine addObject:dectLabel1];
//画黄金三角
NSMutableArray *sanjiaoArray = [NSMutableArray arrayWithArray:triangleArray];
[sanjiaoArray addObject:triangleArray.firstObject];
CALayer *layer = [self drawLineMethod:sanjiaoArray Color:[UIColor whiteColor]];
[self.allViewLine addObject:layer];
// 画黄金三角文字
[self creatFourWord:triangleArray addModel:contentArray.firstObject];
__weak typeof (self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(AnimationDuration1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (weakSelf.stopGCD) { return ;}
// 画四条横线
NSMutableArray<GMFaceCommonPositionModel *> *lineArrayPoint = [NSMutableArray arrayWithObject:triangleArray[2]];
[lineArrayPoint addObjectsFromArray:mouthArray];
[lineArrayPoint addObject:chinArray[1]];
NSArray *lineWidth = @[@(82),@(82),@(175),@(175)];
[lineArrayPoint enumerateObjectsUsingBlock:^(GMFaceCommonPositionModel * _Nonnull point, NSUInteger idx, BOOL * _Nonnull stop) {
GMImageView *lineImage = [GMImageView creatHorizontalPathWithY:point.y];
lineImage.centerX = point.x;
[weakSelf.view addSubview:lineImage];
[UIView animateWithDuration:AnimationDuration animations:^{
lineImage.width = [lineWidth[idx] floatValue];
lineImage.centerX = point.x;
}];
[weakSelf.allViewLine addObject:lineImage];
}];
// 添加带箭头的文字描述
GMFaceCommonPositionModel* linePointModel0 = lineArrayPoint[0];
GMFaceCommonPositionModel* linePointModel1 = lineArrayPoint[1];
GMFaceCommonPositionModel* linePointModel2 = lineArrayPoint[2];
GMFaceCommonPositionModel* linePointModel3 = lineArrayPoint[3];
// 人中长
GMFaceCommonContentModel *rzcModel = contentArray[1];
NSString *rzcString = [NSString stringWithFormat:@"%@%@",rzcModel.title,rzcModel.value];
CGSize rzcStringSize = [rzcString sizeWithAttributes:@{NSFontAttributeName:[UIFont gmFont:9]}];
GMDrawDottedView *racDottedLine = [[GMDrawDottedView alloc] initWithLineType:drawViewTypeVerticalDotted addlabel:wordPlaceRight addDect:rzcString];
racDottedLine.backgroundColor = UIColor.clearColor;
racDottedLine.alpha = 0;
racDottedLine.frame = CGRectMake(linePointModel0.x + 30, linePointModel0.y, rzcStringSize.width + 10, linePointModel1.y - linePointModel0.y);
[weakSelf.view addSubview:racDottedLine];
[weakSelf.allViewLine addObject:racDottedLine];
// 下巴长
GMFaceCommonContentModel *xbcModel = contentArray[2];
NSString *xbcString = [NSString stringWithFormat:@"%@%@",xbcModel.title,xbcModel.value];
CGSize xbcStringSize = [xbcString sizeWithAttributes:@{NSFontAttributeName:[UIFont gmFont:9]}];
GMDrawDottedView *xbcDottedLine = [[GMDrawDottedView alloc] initWithLineType:drawViewTypeVerticalDotted addlabel:wordPlaceRight addDect:xbcString];
xbcDottedLine.backgroundColor = UIColor.clearColor;
xbcDottedLine.alpha = 0;
xbcDottedLine.frame = CGRectMake(linePointModel2.x + 45, linePointModel2.y, xbcStringSize.width + 10, linePointModel3.y - linePointModel2.y);
[weakSelf.view addSubview:xbcDottedLine];
[weakSelf.allViewLine addObject:xbcDottedLine];
[UIView animateWithDuration:AnimationDuration1 animations:^{
racDottedLine.alpha = 1.0;
xbcDottedLine.alpha = 1.0;
}];
});
// 添加描述文字
[self leftPopTitleWith:self.allViewLine AddSubtitle:subTitle Animation:1.25];
[self.allViewLine addObject:self.titleImageView];
}
// 处理第四个页面 面部分析黄金三角文字展示
-(void)creatFourWord:(NSArray *)triangleArray addModel:(GMFaceCommonContentModel *)model{
GMFaceCommonPositionModel *triangleL = triangleArray[0]; // 获取黄金三角最后一个点
GMFaceCommonPositionModel *triangleR = triangleArray[1]; // 获取黄金三角最后一个点
GMFaceCommonPositionModel *triangleP = triangleArray[2]; // 获取黄金三角最后一个点
// 添加黄金三角文字
NSString *title = [NSString stringWithFormat:@"%@\n%@",model.title,model.value];
CGRect titleSize = CGRectMake(triangleP.x - 40, triangleP.y - 60, 80, 35);
GMLabel *dectLabel0 = [self creatLabelWithText:title Font:9 Color:UIColor.whiteColor Size:titleSize];
dectLabel0.numberOfLines = 2;
dectLabel0.alpha = 0.0;
[self.allViewLine addObject:dectLabel0];
// 添加 L 文字
CGRect LSize = CGRectMake(triangleL.x - 15, triangleL.y - 6, 12, 12);
GMLabel *dectLabelL = [self creatLabelWithText:@"L" Font:12 Color:UIColor.whiteColor Size:LSize];
dectLabelL.alpha = 0.0;
[self.allViewLine addObject:dectLabelL];
// 添加 R 文字
CGRect RSize = CGRectMake(triangleR.x + 3, triangleR.y - 6, 12, 12);
GMLabel *dectLabelR = [self creatLabelWithText:@"R" Font:12 Color:UIColor.whiteColor Size:RSize];
dectLabelR.alpha = 0.0;
[self.allViewLine addObject:dectLabelR];
// 添加 P 文字
CGRect PSize = CGRectMake(triangleP.x - 6, triangleP.y + 3, 12, 12);
GMLabel *dectLabelP = [self creatLabelWithText:@"P" Font:12 Color:UIColor.whiteColor Size:PSize];
dectLabelP.alpha = 0.0;
[self.allViewLine addObject:dectLabelP];
[UIView animateWithDuration:AnimationDuration1 animations:^{
dectLabel0.alpha = 1.0;
dectLabelL.alpha = 1.0;
dectLabelR.alpha = 1.0;
dectLabelP.alpha = 1.0;
}];
}
#pragma mark 第五个页面 眉眼分析动画
// 第五个页面 眉眼分析界面
- (void)drawFiveViewAnimation {
// 获取数据, 方便之后使用
NSArray *contnetArray = self.faceModel.analysis.eyeEyebrowInfo.content; // 描述文字数组
NSArray *pointsEye = self.faceModel.analysis.eyeEyebrowInfo.pointsEye; // 眼睛的点坐标
NSArray *pointsEyeBrow = self.faceModel.analysis.eyeEyebrowInfo.pointsEyeBrow; // 眉毛的点坐标
NSArray *subTitle = self.faceModel.analysis.eyeEyebrowInfo.subTitle; // 描述文字
ALFaceRectModel *eyebrowRect = self.faceModel.analysis.eyeEyebrowInfo.eyebrowRect; // 眉毛矩形框
ALFaceRectModel *eyeRect = self.faceModel.analysis.eyeEyebrowInfo.eyeRect; // 眼睛矩形框
GMFaceCommonPositionModel *ptriangle = self.faceModel.analysis.goldTriangleInfo.pointsTriangle[2]; // 获取P点坐标
// 定义缩放比例
CGFloat scale = 1.6;
// 计算P点的新坐标
GMFaceCommonPositionModel *newPoint = [GMFaceCommonPositionModel creatFaceCommonModelWithX:ptriangle.x *scale addY:ptriangle.y *scale];
// 要平移到的最终位置
GMFaceCommonPositionModel *finalPoint = [GMFaceCommonPositionModel creatFaceCommonModelWithX:MAINSCREEN_WIDTH addY:MAINSCREEN_HEIGHT * 0.70];
// 计算便宜量
CGFloat x = finalPoint.x - newPoint.x;
CGFloat y = finalPoint.y - newPoint.y;
// 更改现在的位置
[UIView animateWithDuration:AnimationDuration animations:^{
self.picImageView.frame = CGRectMake(x, y, self.picImageView.frame.size.width * scale, self.picImageView.frame.size.height * scale);
}];
// 计算新的坐标点 并且多加了一个起始点
__weak typeof (self) weakSelf = self;
NSMutableArray *newPointsEyeBrow = [self bigImagePointWithScale:scale offsetX:x offsetY:y poinyArray:pointsEyeBrow];
NSMutableArray *newPointsEye = [self bigImagePointWithScale:scale offsetX:x offsetY:y poinyArray:pointsEye];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(AnimationDuration * 2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (weakSelf.stopGCD) { return ;}
// 画眉毛
[newPointsEyeBrow addObject:newPointsEyeBrow.firstObject];
CALayer *eyeBrowlayer = [weakSelf drawLineMethod:[newPointsEyeBrow copy] Color:Blue_line];
[weakSelf.allViewLine addObject:eyeBrowlayer];
// 画眼睛
[newPointsEye addObject:newPointsEye.firstObject];
CALayer *eyelayer = [weakSelf drawLineMethod:[newPointsEye copy] Color:Blue_line];
[weakSelf.allViewLine addObject:eyelayer];
});
// 原始坐标点
NSArray *eyebrowMidlePoint = [self midlePointWithModel:eyebrowRect];
NSArray *eyeMidlePoint = [self midlePointWithModel:eyeRect]; // 点坐标
// 计算新点的坐标
NSArray *eyebrowNewPoint = [self bigImagePointWithScale:scale offsetX:x offsetY:y poinyArray:eyebrowMidlePoint];
NSArray *eyeNewPoint = [self bigImagePointWithScale:scale offsetX:x offsetY:y poinyArray:eyeMidlePoint];
// 画竖线
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(AnimationDuration * 3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (weakSelf.stopGCD) { return ;}
[weakSelf fiveViewdrawlineWithEye:eyeNewPoint eyebrow:eyebrowNewPoint];
});
// 画虚线
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(AnimationDuration * 4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (weakSelf.stopGCD) { return ;}
[weakSelf fiveViewdrawDottodlineWithEye:eyeNewPoint eyebrow:eyebrowNewPoint modelAry:contnetArray];
});
// 添加描述文字
[self leftPopTitleWith:self.allViewLine AddSubtitle:subTitle Animation:2.5];
}
- (void)fiveViewdrawlineWithEye:(NSArray<GMFaceCommonPositionModel *> *)eyeNewPoint eyebrow:(NSArray<GMFaceCommonPositionModel *> *)eyebrowNewPoint {
NSArray *HLinePointArray = @[eyebrowNewPoint[1],eyebrowNewPoint[3],eyeNewPoint[1],eyeNewPoint[3]];
NSArray *VLinePointArray = @[eyebrowNewPoint[0],eyebrowNewPoint[2],eyeNewPoint[0],eyeNewPoint[2]];
// 获取矩形框的宽和高
CGSize eyebrowSize = [self getWidthAddHightWithRect:eyebrowNewPoint];
CGSize eyeSize = [self getWidthAddHightWithRect:eyeNewPoint];
// 画四条横线
NSArray *widthArray = @[@(eyebrowSize.width),@(eyebrowSize.width),@(eyeSize.width),@(eyeSize.width)];
[HLinePointArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
GMFaceCommonPositionModel *model = (GMFaceCommonPositionModel *)obj;
GMImageView *himageView = [GMImageView creatHorizontalPathWithY:model.y];
himageView.centerX = model.x;
himageView.alpha = 0;
[UIView animateWithDuration:AnimationDuration animations:^{
himageView.width = [widthArray[idx] floatValue] + 30;
himageView.alpha = 1;
himageView.centerX = model.x;
}];
[self.view addSubview:himageView];
[self.allViewLine addObject:himageView];
}];
// 画四条竖线
NSArray *hightArray = @[@(eyebrowSize.height),@(eyebrowSize.height),@(eyeSize.height),@(eyeSize.height)];
[VLinePointArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
GMFaceCommonPositionModel *model = (GMFaceCommonPositionModel *)obj;
GMImageView *vimageView = [GMImageView creatVerticalPathWithX:model.x];
vimageView.centerY = model.y;
[self.view addSubview:vimageView];
[UIView animateWithDuration:AnimationDuration animations:^{
vimageView.height = [hightArray[idx] floatValue] + 30;;
vimageView.centerY = model.y;
}];
[self.allViewLine addObject:vimageView];
}];
}
// 画五个页面的四条虚线
-(void)fiveViewdrawDottodlineWithEye:(NSArray<GMFaceCommonPositionModel *> *)eyeArray eyebrow:(NSArray<GMFaceCommonPositionModel *> *)eyebroArray modelAry:(NSArray *)modelAry {
ALFaceRectModel *eye = [ALFaceRectModel creatRectModelWithTop:eyeArray[1].y right:eyeArray[2].x left:eyeArray[0].x bottom:eyeArray[3].y];
ALFaceRectModel *eyebrow = [ALFaceRectModel creatRectModelWithTop:eyebroArray[1].y right:eyebroArray[2].x left:eyebroArray[0].x bottom:eyebroArray[3].y];
// 画虚线框
NSArray *type = @[@(0),@(1),@(0),@(1)];
NSArray *pleac = @[@(wordPlaceLeft),@(wordPlaceTop),@(wordPlaceRight),@(wordPlaceBottom)];
for (int i = 0; i < modelAry.count; i++ ) {
GMFaceCommonContentModel *model = modelAry[i];
NSString *dect = [NSString stringWithFormat:@"%@\n%@",model.title,model.value];
CGSize dectSize = [dect sizeWithAttributes:@{NSFontAttributeName:[UIFont gmFont:9]}];
// 文字在中间
GMDrawDottedView *drawHView = [[GMDrawDottedView alloc] initWithLineType:[type[i] intValue] addlabel:[pleac[i] intValue] addDect:dect];
drawHView.backgroundColor = [UIColor clearColor];
CGRect frame;
if (i == 0) {
frame = CGRectMake(eyebrow.left - dectSize.width - 15, eyebrow.top, dectSize.width + 10, eyebrow.bottom - eyebrow.top);
}else if (i == 1) {
frame = CGRectMake(eyebrow.left, eyebrow.top - dectSize.height - 15, eyebrow.right - eyebrow.left, dectSize.height + 10);
}else if (i == 2){
frame = CGRectMake(eye.right + 5, eye.top, dectSize.width + 10, eye.bottom -eye.top);
}else{
frame = CGRectMake(eye.left, eye.bottom + 5, eye.right - eye.left, dectSize.height + 10);
}
drawHView.frame = frame;
drawHView.alpha = 0;
[self.view addSubview:drawHView];
[UIView animateWithDuration:AnimationDuration animations:^{
drawHView.alpha = 1;
}];
[self.allViewLine addObject:drawHView];
}
}
#pragma mark 新添加页面动画
- (void)drawAddNewAnimation {
// 人中、嘴唇
NSArray *contnetArray = self.faceModel.analysis.renzhongzuichunkediInfo.content;
NSArray *subTitle = self.faceModel.analysis.renzhongzuichunkediInfo.subTitle;
NSArray *renzhongPoints = self.faceModel.analysis.renzhongzuichunkediInfo.renzhongPoints;
NSArray *zuichunPoints = self.faceModel.analysis.renzhongzuichunkediInfo.zuichunPoints;
NSArray *kediPoints = self.faceModel.analysis.renzhongzuichunkediInfo.kediPoints;
// 瞳距鼻翼宽比
NSArray *tjbContnetArray = self.faceModel.analysis.tongjubiyiInfo.content;
NSArray *tjbSubTitle = self.faceModel.analysis.tongjubiyiInfo.subTitle;
NSArray *tongjuPoints = self.faceModel.analysis.tongjubiyiInfo.tongjuPoints;
NSArray *biyiPoints = self.faceModel.analysis.tongjubiyiInfo.biyiPoints;
// 获取中心点
GMFaceCommonPositionModel *tongjuCenterPoints = [self calculateCenterModelWithArray:tongjuPoints];
GMFaceCommonPositionModel *biyiCenterModel = [self calculateCenterModelWithArray:biyiPoints];
// 鼻翼宽度
CGFloat tongjuWidth = [self calculateWidthWithArray:tongjuPoints];
CGFloat biyiWidth = [self calculateWidthWithArray:biyiPoints];
// 添加横线
NSArray<GMFaceCommonPositionModel *> *hArrry = @[tongjuCenterPoints,biyiCenterModel,renzhongPoints[0],renzhongPoints[1],zuichunPoints[1],kediPoints[1]];
NSArray *widthArray = @[@(tongjuWidth),@(biyiWidth),@(100),@(100),@(100),@(100)];
[self drawSixhLine:hArrry widthArray:widthArray];
// 画中间的描述文字
[self creatVerticalDottedLineWith:tjbContnetArray[0] addArray:@[tongjuCenterPoints,biyiCenterModel]offset:0];
[self creatVerticalDottedLineWith:contnetArray[0] addArray:renzhongPoints offset:0];
[self creatVerticalDottedLineWith:contnetArray[1] addArray:zuichunPoints offset:30];
[self creatVerticalDottedLineWith:contnetArray[2] addArray:kediPoints offset:-20];
// 添加描述文字
NSMutableArray *allsubTitle = [NSMutableArray arrayWithArray:tjbSubTitle];
[allsubTitle addObjectsFromArray:subTitle];
// 添加描述文字
[self leftPopTitleWith:self.allViewLine AddSubtitle:allsubTitle Animation:1.25];
}
// 面孔起源 结束动画
- (void)drawGeneMemoryEndAnimation {
self.stopGCD = YES;
[self.geneEndBgImageView addSubview:self.geneEndImageView];
[self.view addSubview:self.geneEndBgImageView];
NSString *path = [[NSBundle mainBundle] pathForResource:@"gene_end_webp_loading" ofType:@"webp"];
NSData *data = [[NSData alloc] initWithContentsOfFile:path];
UIImage *img = [UIImage sd_imageWithData:data];
self.geneEndImageView.image = img;
[self performSelector:@selector(ignoreMethod) withObject:nil afterDelay:2.1];
}
/**
// 创建竖线 文字在右侧
@param contentModel 描述文字
@param pointArray 画线的点位传两个点过来
*/
-(void)creatVerticalDottedLineWith:(GMFaceCommonContentModel *)contentModel addArray:(NSArray *)pointArray offset:(CGFloat)offset{
GMFaceCommonPositionModel *model0 = pointArray[0];
GMFaceCommonPositionModel *model1 = pointArray[1];
NSString *nbqkString = [NSString stringWithFormat:@"%@%@",contentModel.title,contentModel.value];
CGSize nbqkStringSize = [nbqkString sizeWithAttributes:@{NSFontAttributeName:[UIFont gmFont:9]}];
GMDrawDottedView *nbqkDottedLine = [[GMDrawDottedView alloc] initWithLineType:drawViewTypeVerticalDotted addlabel:wordPlaceRight addDect:nbqkString];
nbqkDottedLine.backgroundColor = UIColor.clearColor;
nbqkDottedLine.alpha = 0;
nbqkDottedLine.frame = CGRectMake(model0.x + offset, model0.y, nbqkStringSize.width + 10, model1.y - model0.y);
[self.view addSubview:nbqkDottedLine];
[self.allViewLine addObject:nbqkDottedLine];
[UIView animateWithDuration:AnimationDuration animations:^{
nbqkDottedLine.alpha = 1;
}];
}
#pragma mark 第六个页面 眉眼分析动画
- (void)drawSixViewAnimation {
NSArray *contnetArray = self.faceModel.analysis.mouthNoseInfo.content;
NSArray *subTitle = self.faceModel.analysis.mouthNoseInfo.subTitle;
NSArray *pointsNose = self.faceModel.analysis.mouthNoseInfo.pointsNose;
NSArray *pointsMouth = self.faceModel.analysis.mouthNoseInfo.pointsMouth;
ALFaceRectModel *mouthRect = self.faceModel.analysis.mouthNoseInfo.mouthRect;
NSArray *contentCxian = self.faceModel.analysis.shuangCxianInfo.content;
NSArray *subTitleCxian = self.faceModel.analysis.shuangCxianInfo.subTitle;
NSArray *pointListCxian = self.faceModel.analysis.shuangCxianInfo.pointList;
// 画嘴角线条
for (GMFacePointsMouthInfoModel *mouthModel in pointsMouth) {
CALayer *eyeBrowlayer = [self drawLineMethod:mouthModel.points Color:Blue_line];
[self.allViewLine addObject:eyeBrowlayer];
}
// 画双C线
CALayer *cxianlayer = [self drawLineMethod:pointListCxian Color:Blue_line];
[self.allViewLine addObject:cxianlayer];
NSArray<GMFaceCommonPositionModel *> *rectArray = [self midlePointWithModel:mouthRect];
GMFaceCommonPositionModel* model0 = rectArray[0];
GMFaceCommonPositionModel* model1 = rectArray[1];
GMFaceCommonPositionModel* model2 = rectArray[2];
GMFaceCommonPositionModel* model3 = rectArray[3];
__weak typeof (self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(AnimationDuration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (weakSelf.stopGCD) { return ;}
// 添加Cxian文字
GMFaceCommonPositionModel *positionModel = pointListCxian[1];
CGRect cxianSize = CGRectMake(positionModel.x + 10, positionModel.y - 15, 100, 45);
GMFaceCommonContentModel *cxianContentModel = contentCxian[0];
GMLabel *dectLabelCxian = [self creatLabelWithText:[NSString stringWithFormat:@"%@\n%@",cxianContentModel.title,cxianContentModel.value] Font:9 Color:UIColor.whiteColor Size:cxianSize];
dectLabelCxian.alpha = 0.0;
dectLabelCxian.numberOfLines = 2;
dectLabelCxian.textAlignment = NSTextAlignmentLeft;
[self.allViewLine addObject:dectLabelCxian];
[UIView animateWithDuration:AnimationDuration1 animations:^{
dectLabelCxian.alpha = 1.0;
}];
// 获取矩形框宽高
CGSize rectSize = [weakSelf getWidthAddHightWithRect:rectArray];
// 画四条竖线
NSArray<GMFaceCommonPositionModel *> *verticalArrry = @[pointsNose[0],pointsNose[1],rectArray[0],rectArray[2]];
NSArray *hightArray = @[@(77),@(77),@(rectSize.height + 30),@(rectSize.height + 30)];
[weakSelf drawSixVerticalLine:verticalArrry hightArray:hightArray];
// 画三条横线
NSArray *realLine = weakSelf.faceModel.analysis.santingInfo.realLinePoints;
NSArray<GMFaceCommonPositionModel *> *hArrry = @[realLine[1],rectArray[1],rectArray[3]];
NSArray *widthArray = @[@(115),@(rectSize.width + 30),@(rectSize.width + 30)];
[weakSelf drawSixhLine:hArrry widthArray:widthArray];
// 画带箭头的线条
[weakSelf drawDottodLineWithPointArray:pointsNose content:contnetArray[0] type:drawViewTypeHorizontalDotted place:wordPlaceTop];
GMFaceCommonPositionModel* newModel0 = model1;
newModel0.x = model2.x;
NSArray *rightArray = @[newModel0, model3];
[weakSelf drawDottodLineWithPointArray:rightArray content:contnetArray[1] type:drawViewTypeVerticalDotted place:wordPlaceRight];
GMFaceCommonPositionModel* newModel1 = model0;
newModel1.y = model3.y + 15;
NSArray *bootmArray = @[newModel1, model2];
[weakSelf drawDottodLineWithPointArray:bootmArray content:contnetArray[2] type:drawViewTypeHorizontalDotted place:wordPlaceBottom];
});
// 添加描述文字
NSMutableArray *allsubTitle = [NSMutableArray arrayWithArray:subTitle];
[allsubTitle addObjectsFromArray:subTitleCxian];
// 添加描述文字
[self leftPopTitleWith:self.allViewLine AddSubtitle:allsubTitle Animation:1.25];
}
- (void)drawDottodLineWithPointArray:(NSArray<GMFaceCommonPositionModel *> *)pointArray content:(GMFaceCommonContentModel *)contentModel type:(GMLineType)lineType place:(GMWordPlace)place {
NSString *dect = [NSString stringWithFormat:@"%@\n%@",contentModel.title,contentModel.value];
CGSize dectSize = [dect sizeWithAttributes:@{NSFontAttributeName:[UIFont gmFont:9]}];
GMDrawDottedView *drawHView = [[GMDrawDottedView alloc] initWithLineType:lineType addlabel:place addDect:dect];
drawHView.backgroundColor = [UIColor clearColor];
CGRect frame;
if (lineType == drawViewTypeHorizontalDotted) {
if (place == wordPlaceTop) {
frame = CGRectMake(pointArray[0].x, (pointArray[0].y - dectSize.height - 10),pointArray[1].x - pointArray[0].x, dectSize.height + 10);
}else{
frame = CGRectMake(pointArray[0].x, (pointArray[0].y - 10),pointArray[1].x - pointArray[0].x, dectSize.height + 10);
}
}else{
frame = CGRectMake(pointArray[0].x + 5, pointArray[0].y, dectSize.width + 10, pointArray[1].y - pointArray[0].y);
}
drawHView.frame = frame;
drawHView.alpha = 0;
[self.view addSubview:drawHView];
[UIView animateWithDuration:AnimationDuration1 animations:^{
drawHView.alpha = 1;
}];
[self.allViewLine addObject:drawHView];
}
// 画横线
- (void)drawSixhLine:(NSArray<GMFaceCommonPositionModel *> *)pointXArray widthArray:(NSArray<NSNumber *> *)widthArray{
for (int i = 0; i < pointXArray.count; i++) {
GMFaceCommonPositionModel *model = pointXArray[i];
GMImageView *vimageView = [GMImageView creatHorizontalPathWithY:model.y];
vimageView.centerX = model.x;
[self.view addSubview:vimageView];
[UIView animateWithDuration:AnimationDuration animations:^{
vimageView.width = [widthArray[i] floatValue];
vimageView.centerX = model.x;
}];
[self.allViewLine addObject:vimageView];
}
}
- (void)drawSixVerticalLine:(NSArray<GMFaceCommonPositionModel *> *)pointXArray hightArray:(NSArray<NSNumber *> *)hightArray{
for (int i = 0; i < pointXArray.count; i++) {
GMFaceCommonPositionModel *model = pointXArray[i];
GMImageView *vimageView = [GMImageView creatVerticalPathWithX:model.x];
vimageView.centerY = model.y;
[self.view addSubview:vimageView];
[UIView animateWithDuration:AnimationDuration animations:^{
vimageView.height = [hightArray[i] floatValue];
vimageView.centerY = model.y;
}];
[self.allViewLine addObject:vimageView];
}
}
#pragma mark 辅助方法
// 展示分析图片
- (void)addTitleImageView:(NSString *)imageName {
self.titleImageView = [[GMImageView alloc] initWithImage:[UIImage imageNamed:imageName]];
_titleImageView.contentMode = UIViewContentModeScaleAspectFit;
_titleImageView.frame = CGRectMake(15, -55, 248, 55);
[UIView animateWithDuration:AnimationDuration animations:^{
_titleImageView.transform = CGAffineTransformMakeTranslation(0, [OCNavigationBar barHeight] + 71);
}];
[self.view addSubview:_titleImageView];
}
// 计算中心点坐标
-(NSArray *)midlePointWithModel:(ALFaceRectModel *)model{
GMFaceCommonPositionModel *midleLeft = [GMFaceCommonPositionModel creatFaceCommonModelWithX:model.left addY:(model.bottom - model.top) / 2 + model.top];
GMFaceCommonPositionModel *midleTop = [GMFaceCommonPositionModel creatFaceCommonModelWithX:(model.right - model.left) / 2 + model.left addY:model.top];
GMFaceCommonPositionModel *midleRight = [GMFaceCommonPositionModel creatFaceCommonModelWithX:model.right addY:(model.bottom - model.top) / 2 + model.top];
GMFaceCommonPositionModel *midleBottom = [GMFaceCommonPositionModel creatFaceCommonModelWithX:(model.right - model.left) / 2 + model.left addY:model.bottom];
return [NSArray arrayWithObjects:midleLeft,midleTop,midleRight,midleBottom, nil];
}
// 计算放大后的点位置
-(NSMutableArray *)bigImagePointWithScale:(CGFloat)scale offsetX:(CGFloat)offsetX offsetY:(CGFloat)offsetY poinyArray:(NSArray *)pointArray{
NSMutableArray *newPointArray = [NSMutableArray array];
[pointArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
GMFaceCommonPositionModel *point = (GMFaceCommonPositionModel *)obj;
GMFaceCommonPositionModel *newPoint = [GMFaceCommonPositionModel new];
newPoint.x = point.x * scale + offsetX;
newPoint.y = point.y * scale + offsetY;
[newPointArray addObject:newPoint];
}];
return newPointArray;
}
// 更新背景图片的尺寸
- (void)updatePicImageViewframeP {
[UIView animateWithDuration:AnimationDuration animations:^{
self.picImageView.frame = self.view.frame;
}];
}
// 创建描述文字
- (GMLabel *)creatLabelWithText:(NSString *)text Font:(CGFloat)font Color:(UIColor *)color Size:(CGRect)frame {
GMLabel *label = [GMLabel labelWithTextAlignment:NSTextAlignmentCenter backgroundColor:UIColor.clearColor textColor:color fontSize:font];
label.shadowColor = [UIColor colorWithHexString:@"000000" alpha:0.3];
label.shadowOffset = CGSizeMake(0, 1);
label.layer.shadowRadius = 0.5;
label.frame = frame;
label.text = text;
[self.view addSubview:label];
return label;
}
// 给一个矩形框,返回一个宽和高
- (CGSize)getWidthAddHightWithRect:(NSArray<GMFaceCommonPositionModel *> *)rectArray{
NSAssert(rectArray.count == 4, @"这个数组必须传过来四个点");
CGFloat rectWidth = rectArray[2].x - rectArray[0].x;
CGFloat recthight = rectArray[3].y - rectArray[1].y;
return CGSizeMake(rectWidth, recthight);
}
// 清除页面上的线条
- (void)clearFristViewLineView {
for (id viewOrLayer in self.allViewLine) {
if ([viewOrLayer isKindOfClass:[UIView class]]) {
UIView *view = (UIView *)viewOrLayer;
[UIView animateWithDuration:AnimationDuration animations:^{
view.alpha = 0;
} completion:^(BOOL finished) {
[view removeAllSubviews];
}];
}else if([viewOrLayer isKindOfClass:[CALayer class]]){
CALayer *layer = (CALayer *)viewOrLayer;
[CATransaction begin];
[CATransaction setAnimationDuration:AnimationDuration];
layer.opacity = 0.0;
[CATransaction commit];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(AnimationDuration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[layer removeFromSuperlayer];
});
}
}
}
// 画线
- (CAShapeLayer *)drawLineMethod:(NSArray<GMFaceCommonPositionModel *>*)positionModelArray Color:(UIColor *)color{
UIBezierPath *path = [UIBezierPath bezierPath];
[positionModelArray enumerateObjectsUsingBlock:^(GMFaceCommonPositionModel *value, NSUInteger idx, BOOL * _Nonnull stop) {
GMFaceCommonPositionModel *pointModel = value;
if (idx == 0) {
[path moveToPoint:CGPointMake(pointModel.x, pointModel.y)];
}else {
[path addLineToPoint:CGPointMake(pointModel.x, pointModel.y)];
}
}];
// 创建CAShapeLayer
CAShapeLayer *layer = [CAShapeLayer layer];
layer.lineWidth = 1.0f;
layer.fillColor = [UIColor clearColor].CGColor;
layer.strokeColor = color.CGColor;
[self.view.layer addSublayer:layer];
layer.path = path.CGPath;
// 创建Animation
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
animation.fromValue = @(0.0);
animation.toValue = @(1.0);
layer.autoreverses = NO;
animation.duration = 0.75f;
// 设置layer的animation
[layer addAnimation:animation forKey:nil];
return layer;
}
#pragma mark 左侧弹出的描述-方法封装
- (void)leftPopTitleWith:(NSMutableArray *)viewLineArray AddSubtitle:(NSArray *)subTitle Animation:(CGFloat)duration{
__weak typeof (self) weakSelf = self;
for (int i = 0; i < subTitle.count; i++) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((duration + 0.25 * i) * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (weakSelf.stopGCD) { return ;}
GMFaceCommonSubtitleModel *subTitleModel = subTitle[i];
CGFloat sumHight = TitleToTopHight;
for (int j = 0; j < i; j ++) {
GMFaceCommonSubtitleModel *titleModel = subTitle[j];
sumHight += titleModel.hight + 20;
}
GMTitleView *tView = [weakSelf addDectTitleNameWithModel:subTitleModel viewFrame:CGRectMake(-120, sumHight, 120, subTitleModel.hight)];
[viewLineArray addObject:tView];
});
}
}
// 添加描述文字
- (GMTitleView *)addDectTitleNameWithModel:(GMFaceCommonSubtitleModel *)tempModel viewFrame:(CGRect)frame {
GMTitleView *tView = [[GMTitleView alloc] initWithModel:tempModel];
tView.alpha = 0;
tView.backgroundColor = [UIColor clearColor];
tView.frame = frame;
[self.view addSubview:tView];
[UIView animateWithDuration:AnimationDuration animations:^{
tView.transform = CGAffineTransformMakeTranslation(135, 0);
tView.alpha = 1;
}];
return tView;
}
#pragma mark 创建返回和跳过按钮, 及处理逻辑。
- (GMButton *)backBtn {
if (!_backBtn) {
_backBtn = [GMButton buttonWithType:(UIButtonTypeCustom)];
_backBtn.enableAdaptive = YES;
_backBtn.alpha = 1.0;
[_backBtn setImage:[UIImage imageNamed:@"NewFaceBack"] forState:UIControlStateNormal];
[_backBtn addTarget:self action:@selector(backMethod) forControlEvents:(UIControlEventTouchUpInside)];
}
return _backBtn;
}
- (GMButton *)ignoreBtn {
if (!_ignoreBtn) {
_ignoreBtn = [GMButton buttonWithType:(UIButtonTypeCustom)];
_ignoreBtn.enableAdaptive = YES;
_ignoreBtn.alpha = 1.0;
[_ignoreBtn setTitle:@"跳过" forState:(UIControlStateNormal)];
[_ignoreBtn.titleLabel setFont:[UIFont gmFont:14]];
[_ignoreBtn setTitleColor:UIColor.whiteColor forState:(UIControlStateNormal)];
[_ignoreBtn addTarget:self action:@selector(ignoreMethod) forControlEvents:(UIControlEventTouchUpInside)];
}
return _ignoreBtn;
}
- (GMImageView *)geneEndBgImageView {
if (!_geneEndBgImageView) {
_geneEndBgImageView = [[GMImageView alloc] initWithImage:[UIImage imageNamed:@"gene_end_bj_webp_loading@3x"]];
_geneEndBgImageView.contentMode = UIViewContentModeScaleToFill;
_geneEndBgImageView.frame = CGRectMake(0, 0, MAINSCREEN_WIDTH, MAINSCREEN_HEIGHT);
}
return _geneEndBgImageView;
}
- (SDAnimatedImageView *)geneEndImageView {
if (!_geneEndImageView) {
_geneEndImageView = [[SDAnimatedImageView alloc] init];
_geneEndImageView.contentMode = UIViewContentModeScaleAspectFit;
_geneEndImageView.runLoopMode = NSDefaultRunLoopMode;
_geneEndImageView.frame = CGRectMake(0, 0, MAINSCREEN_WIDTH, MAINSCREEN_HEIGHT);
}
return _geneEndImageView;
}
- (void)backMethod {
// 返回到具体页面
self.stopGCD = YES;
BOOL isPop = YES;
for (UIViewController *vc in AppDelegate.navigation.childViewControllers) {
if ([vc isKindOfClass:[NewFaceShotController class]]){
isPop = NO;
[AppDelegate.navigation popToViewController:vc animated:YES];
return;
}
}
if (isPop) {
[AppDelegate.navigation popToRootViewControllerAnimated:YES];
}
}
- (void)ignoreMethod {
self.stopGCD = YES;
[Phobos track:@"on_click_button" attributes:@{@"page_name":SafeString(self.pageName),
@"button_name":SafeString(@"skip")}];
if (self.isGene) { // 面孔起源
GMGeneMemoryResultViewController *vc = [[GMGeneMemoryResultViewController alloc] init];
[AppDelegate.navigation pushViewController:vc animated:YES];
} else {
GMFaceInfoShareViewController *vc = [[GMFaceInfoShareViewController alloc] init];
vc.faceModel = self.faceModel;
[AppDelegate.navigation pushViewController:vc animated:YES];
}
}
-(NSMutableArray *)allViewLine{
if (!_allViewLine) {
_allViewLine = [NSMutableArray array];
}
return _allViewLine;
}
-(void)dealloc{
NSLog(@"%s", __func__);
}
@end
//
// GMFaceReplayResultVC.h
// Gengmei
//
// Created by 刘鹿杰 on 2019/7/26.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@class GMFaceV3Model;
@class GMFaceReplayModel;
@interface GMFaceReplayResultVC : WMBaseViewController
- (instancetype)initWithModel:(GMFaceV3Model *)faceModel replayModel:(GMFaceReplayModel *)replayModel;
@end
NS_ASSUME_NONNULL_END
//
// GMFaceReplayResultVC.m
// Gengmei
//
// Created by 刘鹿杰 on 2019/7/26.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMFaceReplayResultVC.h"
#import "GMScreenRecorderManager.h"
#import "GMFaceInfoShareViewController.h"
#import "GMShareView.h"
#import "GMFaceReplayModel.h"
#import "WMShareObject.h"
#import "GMFaceReplayViewController.h"
@interface GMFaceReplayResultVC ()<GMScreenRecorderDelegate, CAAnimationDelegate, GMShareViewDelegate>
@property (nonatomic, strong) GMFaceReplayModel *repalyModel; // 数据模型
@property (nonatomic, strong) GMFaceV3Model *faceModel; // 脸型点位数据模型
@property (nonatomic, strong) WMShareObject *shareObject; // 分享数据获取
@property (nonatomic, strong) UIImageView *resultImageView; // 结果页面展示图片
@property (nonatomic, strong) GMButton *backBtn; // 返回按钮
@property (nonatomic, strong) GMShareView *shareView; // 分享view
@end
@implementation GMFaceReplayResultVC
- (instancetype)initWithModel:(GMFaceV3Model *)faceModel replayModel:(GMFaceReplayModel *)replayModel{
if (self = [super init]) {
self.pageName = @"scan_report_video";
self.repalyModel = replayModel;
self.faceModel = faceModel;
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationBar.hidden = YES;
self.fd_interactivePopDisabled = YES;
self.view.backgroundColor = [UIColor whiteColor];
// 加载分享资源
__weak typeof (self) weakSelf = self;
[GMNetworking requestOCWithApi:API_FACE_SHARE_DATA method:GMHTTPMethodGet parameters:nil completion:^(GMResponseOC * _Nonnull responseObject) {
weakSelf.shareObject = [[WMShareObject alloc] initWithDictionary:responseObject.data error:nil];
weakSelf.shareView.shareObject = weakSelf.shareObject;
}];
// 添加结果页面图片
if (self.repalyModel.resultImage) {
self.resultImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, MAINSCREEN_WIDTH, [self getResultImageHeight])];
_resultImageView.image = self.repalyModel.resultImage;
[self.view addSubview:self.resultImageView];
}
// 设置录屏代理方法
if (self.repalyModel.tapType != GMReplayViewTapPreview) {
[GMScreenRecorderManager sharedManager].delegate = self;
}
[self addbackBtn]; // 添加返回按钮
}
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
CGFloat imageOffset = [self getResultImageHeight] - MAINSCREEN_HEIGHT;
CGFloat offsetY = (imageOffset > 0) ? (-imageOffset) : 0;
if (offsetY < 0) {
[UIView animateWithDuration:1.5 animations:^{
self.resultImageView.transform = CGAffineTransformMakeTranslation(0, offsetY);
}];
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.7 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self animationStop];
});
}
- (CGFloat)getResultImageHeight {
return (self.repalyModel.resultImage.size.height * MAINSCREEN_WIDTH) / self.repalyModel.resultImage.size.width;
}
- (void)addbackBtn{
[self.view addSubview:self.backBtn];
[self.backBtn mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(20);
make.centerY.equalTo(self.view.mas_top).offset([OCNavigationBar navigationItemCenterY]);
}];
}
#pragma mark - 代理方法
-(void)animationStop{
self.backBtn.hidden = NO;
if (self.repalyModel.tapType == GMReplayViewTapPreview) {
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(shareBbtClick)];
[self.view addGestureRecognizer:recognizer];
[self.shareView showWithShareUrl:@"GMFaceReplayResultVC"];
}else if(self.repalyModel.tapType == GMReplayViewTapWechat){ // 微信录制结束
[self showLoading:nil];
[GMScreenRecorderManager stopRecordWithSave];
}else{
[self showLoading:nil];
[GMScreenRecorderManager stopRecordWithSave];
}
}
// 屏幕结束录制时发现未知错误 -- 文字提醒
-(void)didStopRecordWithErrorString:(NSString *)errorString {
[self showWarning:errorString];
// 返回到上一页
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self backMethod];
});
}
// 保存视频成功,并拿到保存到相册的视频URL
- (void)saveVideoSuscessWithAssetURl:(AVURLAsset *)assetURL {
[self toast:@"视频保存成功!"];
self.backBtn.hidden = NO;
// 发送通知,通知结果页面视频保存成功
[[NSNotificationCenter defaultCenter] postNotificationName:@"GMFaceReplayResultVC" object:assetURL];
self.shareView.shareObject.videoUrl = [NSString stringWithFormat:@"%@",assetURL.URL];
self.shareView.shareUrl = @"GMFaceReplayResultVC";
// 获取分享数据
if (self.repalyModel.tapType == GMReplayViewTapDouyin){ // 抖音
[Phobos track:@"event_status" attributes:@{@"page_name" : SafeValue(self.pageName), @"event_name" : @"douyin_video_record", @"event_status": @"success"}];
[self.shareView shareWithType:GMSharePlatformDouyin];
}else{ // 微信
[Phobos track:@"event_status" attributes:@{@"page_name" : SafeValue(self.pageName), @"event_name" : @"wechartline_video_record", @"event_status": @"success"}];
[self.shareView shareWithType:GMSharePlatformWechatTimeline];
}
// 返回到扫脸页面
for (UIViewController *vc in AppDelegate.navigation.childViewControllers) {
if ([vc isKindOfClass:[GMFaceInfoShareViewController class]]){
[AppDelegate.navigation popToViewController:vc animated:NO];
return;
}
}
}
#pragma mark - GMShareViewDelegate
- (NSMutableDictionary *)fetchSharePublishContent:(GMSharePlatform)shareType {
id shareImage = nil;
GMShareContentType contentType = GMShareContentTypeAuto;
WMShareBasicObject * object = [WMShareBasicObject new];
NSString *image = _shareObject.image;
if (image.length != 0) {
shareImage = [GMShareSDK compressWithUrl:image];
}else{
// 使用app logo做图标
shareImage = [UIImage imageNamed:@"icon"];
}
// 微信会话分享且微信小程序内容不为空的时候,认为是微信小程序分享
if (shareType == GMSharePlatformWechatSession && [_shareObject.wechatmini.path isNonEmpty]) {
shareType = GMSharePlatformWechatMiniProgram;
}
switch (shareType) {
case GMSharePlatformSinaWeibo:
object = _shareObject.weibo;
break;
case GMSharePlatformWechatSession:
object = _shareObject.wechat;
break;
case GMSharePlatformWechatTimeline:
object = _shareObject.wechatline;
break;
case GMSharePlatformQQFriend:
object = _shareObject.qq;
break;
case GMSharePlatformDouyin:
object = _shareObject.douyin;
break;
default:
break;
}
NSMutableDictionary *shareParams = [NSMutableDictionary dictionary];
if (shareType == GMSharePlatformWechatMiniProgram){
[shareParams shareSetupMiniProgramShareParamsByUrl:_shareObject.url
userName:_shareObject.wechatmini.userName
path:_shareObject.wechatmini.path
title:_shareObject.wechatmini.title
description:_shareObject.wechatmini.desc
thumbImage:_shareObject.wechatmini.thumbImageUrl
hdImageData:_shareObject.wechatmini.hdImageUrl];
} else {
if (_shareObject.videoUrl.isNonEmpty) {
// 分享视频
[shareParams shareSetupShareParamsByText:object.content images:shareImage url:[NSURL URLWithString:_shareObject.url] title:object.title videoUrl:[NSURL URLWithString:_shareObject.videoUrl] type:contentType];
} else {
[shareParams shareSetupShareParamsByText:object.content
images:shareImage
url:[NSURL URLWithString:_shareObject.url]
title:object.title
type:contentType];
}
}
return shareParams;
}
#pragma mark - 添加按钮
- (GMButton *)backBtn {
if (!_backBtn) {
_backBtn = [GMButton buttonWithType:(UIButtonTypeCustom)];
_backBtn.enableAdaptive = YES;
_backBtn.alpha = 1.0;
_backBtn.hidden = YES;
[_backBtn setImage:[UIImage imageNamed:@"NewFaceBackBlack"] forState:UIControlStateNormal];
[_backBtn addTarget:self action:@selector(backMethod) forControlEvents:(UIControlEventTouchUpInside)];
}
return _backBtn;
}
- (void)backMethod {
// 返回到具体页面
BOOL isPop = YES;
for (UIViewController *vc in AppDelegate.navigation.childViewControllers) {
if ([vc isKindOfClass:[GMFaceInfoShareViewController class]]){
isPop = NO;
[AppDelegate.navigation popToViewController:vc animated:YES];
return;
}
}
if (isPop) {
[AppDelegate.navigation popToRootViewControllerAnimated:YES];
}
}
#pragma mark - 添加GMShareView
- (GMShareView *)shareView{
if (!_shareView) {
_shareView = [[GMShareView alloc] init];
_shareView.delegate = self;
}
return _shareView;
}
- (void)willShowShareView:(GMShareView *)shareView {
shareView.showCopyLink = NO;
shareView.showRefresh = NO;
}
- (void)shareVideo:(GMSharePlatform)shareType {
GMFaceReplayModel *rmodel = self.repalyModel;
if (shareType == GMSharePlatformDouyin) {
rmodel.type = @"douyin";
}else {
rmodel.type = @"wechat";
}
NSString *timeStrKey = [NSString stringWithFormat:@"%@TimeKey",rmodel.type];
NSString *timeStr = [GMCache fetchObjectAtDiskWithkey:timeStrKey];
NSDate *date = [NSDate date];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyyMMdd"];
NSString *dateTime = [formatter stringFromDate:date];
if ([timeStr isEqualToString:dateTime]) { // 是同一天
[self pushToGMFaceReplayViewController:shareType addRModel:rmodel];
}else{ // 不是同一天
[GMCache storeObjectAtDiskWithkey:timeStrKey object:dateTime];
[self toast:@"分享视频需要录制您的屏幕,录制期间请勿退出界面"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self pushToGMFaceReplayViewController:shareType addRModel:rmodel];
});
}
}
-(void)pushToGMFaceReplayViewController:(GMSharePlatform)shareType addRModel:(GMFaceReplayModel *)rModel{
GMFaceReplayViewController * vc = [[GMFaceReplayViewController alloc] initWithModel:self.faceModel replayModel:rModel];
[AppDelegate.navigation pushViewController:vc animated:YES];
}
-(void)cancleShareView{
[self backMethod];
}
-(void)shareBbtClick{
// 添加分析弹框
[self.shareView showWithShareUrl:@"GMFaceReplayResultVC"];
}
-(void)dealloc{
NSLog(@"%s",__func__);
}
@end
//
// GMFaceReplayViewController.h
// Gengmei
//
// Created by 刘鹿杰 on 2019/7/24.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMFaceBaseVC.h"
@class GMFaceV3Model;
@class GMFaceReplayModel;
NS_ASSUME_NONNULL_BEGIN
@interface GMFaceReplayViewController : GMFaceBaseVC
- (instancetype)initWithModel:(GMFaceV3Model *)faceModel replayModel:(GMFaceReplayModel *)replayModel;
@end
NS_ASSUME_NONNULL_END
//
// GMFaceReplayViewController.m
// Gengmei
//
// Created by 刘鹿杰 on 2019/7/24.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMFaceReplayViewController.h"
#import "GMFaceV3Model.h"
#import "GMMarkView.h"
#import "ALTransFormFaceUtil.h"
#import "GMFaceV3Util.h"
#import "GMDrawDottedView.h"
#import "GMImageView+GMFace.h"
#import "GMTitleView.h"
#import "GMFaceReplayResultVC.h"
#import "GMScreenRecorderManager.h"
#import "GMFaceReplayModel.h"
#import "GMFaceInfoShareViewController.h"
#define AnimationDuration 0.35 // 动画执行时间
#define Blue_line [UIColor colorWithHexString:@"0x4EC4CD" alpha:0.6]
#define TitleToTopHight [OCNavigationBar barHeight] + 101
@interface GMFaceReplayViewController ()<GMScreenRecorderDelegate>
@property (nonatomic, strong) GMFaceV3Model *faceModel;
@property (nonatomic, strong) GMFaceReplayModel *replayModel;
// 记录第一个页面侧面弹出框View
@property (nonatomic, strong)NSMutableArray *fristLeftPopView;
@end
@implementation GMFaceReplayViewController
- (instancetype)initWithModel:(GMFaceV3Model *)faceModel replayModel:(nonnull GMFaceReplayModel *)replayModel {
if (self = [super init]) {
self.faceModel = faceModel;
self.replayModel = replayModel;
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationBar.hidden = YES;
self.fd_interactivePopDisabled = YES;
self.view.backgroundColor = [UIColor colorWithHexString:@"666666"];
[self addImageView]; // 设置ImageView
if (self.replayModel.tapType != GMReplayViewTapPreview) {
[GMScreenRecorderManager sharedManager].delegate = self;
}
}
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
__weak typeof(self) weakSelf = self;
if (self.replayModel.tapType != GMReplayViewTapPreview) {
[GMScreenRecorderManager startRecord:NO suscess:^{ // 如果开启成功,开始展示动画
[weakSelf setupSubView];
}];
}else{
[self setupSubView];
}
}
#pragma mark - 主要代码
// 添加控件
- (void)setupSubView {
[self addMaskView]; // 添加遮罩层
[self addTitleImageView:@"facev3_analyse"]; // 添加面部分析图片
[self controlAnimation]; // 控制动画
}
// 添加picImageView
- (void)addImageView {
self.picImageView = [[GMImageView alloc] initWithImage:self.faceModel.image];
self.picImageView.contentMode = UIViewContentModeScaleAspectFill;
self.picImageView.frame = self.view.frame;
[self.view addSubview:self.picImageView];
}
// 添加遮罩层
- (void)addMaskView {
GMMarkView * markView = [[GMMarkView alloc] initWithType:self.replayModel];
markView.backgroundColor = [UIColor colorWithHexString:@"000000" alpha:0.25];
markView.alpha = 0;
markView.frame = CGRectMake(0, 0, MAINSCREEN_WIDTH, MAINSCREEN_HEIGHT);
[self.view addSubview:markView];
[UIView animateWithDuration:AnimationDuration animations:^{
markView.alpha = 1;
}];
}
// 展示分析图片
- (void)addTitleImageView:(NSString *)imageName {
self.titleImageView = [[GMImageView alloc] initWithImage:[UIImage imageNamed:imageName]];
self.titleImageView.contentMode = UIViewContentModeScaleAspectFit;
self.titleImageView.frame = CGRectMake(15, -55, 248, 55);
[UIView animateWithDuration:AnimationDuration animations:^{
self.titleImageView.transform = CGAffineTransformMakeTranslation(0, [OCNavigationBar barHeight] + 71);
}];
[self.view addSubview:self.titleImageView];
}
#pragma mark - 动画控制
// 控制动画效果
- (void)controlAnimation {
__weak typeof(self) weakSelf = self; // 展示面部分析动画后, 停留0.5s,动画耗时0.75s 共用 1.5s
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.7 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (!weakSelf || weakSelf.stopGCD) { return ;}
[weakSelf drawOneViewAnimation]; // 共耗时 0.5s
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (!weakSelf || weakSelf.stopGCD) { return ;}
[weakSelf clearFristViewLineView]; // 耗时0.75s
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(AnimationDuration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (!weakSelf || weakSelf.stopGCD) { return ;}
[weakSelf addChinLineAddsanjiao]; // 逻辑待完善
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (!weakSelf || weakSelf.stopGCD) { return ;}
[weakSelf controlVcAnimationFive];
});
});
});
});
}
// 控制第五个页面的动画
- (void)controlVcAnimationFive {
[self clearFristViewLineView]; // 耗时0.75s
__weak typeof(self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(AnimationDuration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (!weakSelf || weakSelf.stopGCD) { return ;}
[weakSelf addTitleImageView:@"facev3_eye_analyse"]; // 0.35s
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(AnimationDuration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (!weakSelf || weakSelf.stopGCD) { return ;}
[weakSelf drawFiveViewAnimation]; // 3.75s
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.85 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (!weakSelf || weakSelf.stopGCD) { return ;}
[weakSelf controlVcAddNewAnimation];
});
});
});
}
// 控制新添加页面的动画
- (void)controlVcAddNewAnimation {
__weak typeof(self) weakSelf = self;
[weakSelf clearFristViewLineView];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(AnimationDuration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (!weakSelf || weakSelf.stopGCD) { return ;}
[weakSelf updatePicImageViewframeP]; // 更改回来控件的frame
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(AnimationDuration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (!weakSelf || weakSelf.stopGCD) { return ;}
[weakSelf drawAddNewAnimation];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (!weakSelf || weakSelf.stopGCD) { return ;}
[weakSelf controlVcAnimationSix];
});
});
});
}
// 控制第六个页面的动画
- (void)controlVcAnimationSix {
__weak typeof(self) weakSelf = self;
[weakSelf clearFristViewLineView]; // 更改回来控件的frame
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(AnimationDuration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (!weakSelf || weakSelf.stopGCD) { return ;}
[weakSelf addTitleImageView:@"facev3_mouth_analyse"]; // 0.75s
[weakSelf.allViewLine addObject:weakSelf.titleImageView];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(AnimationDuration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (!weakSelf || weakSelf.stopGCD) { return ;}
[weakSelf drawSixViewAnimation]; // 1.75s
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (!weakSelf || weakSelf.stopGCD) { return ;}
GMFaceReplayResultVC *vc = [[GMFaceReplayResultVC alloc] initWithModel:weakSelf.faceModel replayModel:weakSelf.replayModel];
[weakSelf.navigationController pushViewController:vc animated:YES];
});
});
});
}
#pragma mark - 面部分析
- (void)drawOneViewAnimation {
NSArray *pointsRight = self.faceModel.analysis.featureInfo.pointsRight; // 脸右侧点位置
NSArray *mouthArray = self.faceModel.analysis.goldTriangleInfo.pointsMouth; // 获取嘴角两个点
NSArray *subTitle1 = self.faceModel.analysis.featureInfo.subTitle; // 脸型描述文字
NSArray *subTitle2 = self.faceModel.analysis.goldTriangleInfo.subTitle; // 黄金三角页面描述文字
NSArray *triangleArray = self.faceModel.analysis.goldTriangleInfo.pointsTriangle; // 获取黄金三角三个点
NSArray *chinArray = self.faceModel.analysis.goldTriangleInfo.pointsChin; // 获取下巴的三个点
NSArray *contentArray = self.faceModel.analysis.goldTriangleInfo.content; // 获取所有角度
// 新添加的字段
NSArray *nbSubTitle = self.faceModel.analysis.niebuquankuanInfo.subTitle;
NSArray *niebuPoints = self.faceModel.analysis.niebuquankuanInfo.niebuPoints; // 添加画脸的线
NSArray *quankuanPoints = self.faceModel.analysis.niebuquankuanInfo.quankuanPoints;
NSArray *nbqkContentArray = self.faceModel.analysis.niebuquankuanInfo.content;
// 画右侧轮廓
CALayer *layer = [self drawLineMethod:pointsRight Color:Blue_line];
[self.allViewLine addObject:layer];
// 颞部数据处理
GMFaceCommonPositionModel *niebuCenterModel = [self calculateCenterModelWithArray:niebuPoints];
GMFaceCommonPositionModel *quankuanCenterModel = [self calculateCenterModelWithArray:quankuanPoints];
GMFaceCommonContentModel *nbqkModel = nbqkContentArray[0];
// 画6条横线
NSMutableArray<GMFaceCommonPositionModel *> *lineArrayPoint = [NSMutableArray arrayWithObject:triangleArray[2]];
[lineArrayPoint addObjectsFromArray:mouthArray];
[lineArrayPoint addObject:chinArray[1]];
[lineArrayPoint addObject:niebuCenterModel];
[lineArrayPoint addObject:quankuanCenterModel];
// 添加颞部新增横线
[self creatHorizontalLine:lineArrayPoint];
// 添加带箭头的文字描述 -- 人中长 Or 下巴长
[self creatVorticalDescLine:@[lineArrayPoint[0],lineArrayPoint[1]] content:contentArray[1] offset:30];
[self creatVorticalDescLine:@[lineArrayPoint[2],lineArrayPoint[3]] content:contentArray[2] offset:45];
// 展示颞部比例
[self creatVorticalDescLine:@[niebuCenterModel,quankuanCenterModel] content:nbqkModel offset:30];
// 添加描述文字
NSMutableArray *subTitle = [NSMutableArray arrayWithArray:subTitle1];
[subTitle addObjectsFromArray:nbSubTitle];
[subTitle addObjectsFromArray:subTitle2];
[self leftPopTitleWith:self.fristLeftPopView AddSubtitle:subTitle Animation:0];
}
-(void)addChinLineAddsanjiao {
NSArray *triangleArray = self.faceModel.analysis.goldTriangleInfo.pointsTriangle; // 获取黄金三角三个点
NSArray *chinArray = self.faceModel.analysis.goldTriangleInfo.pointsChin; // 获取下巴的三个点
NSArray *contentArray = self.faceModel.analysis.goldTriangleInfo.content; // 获取所有角度
// 添加下巴及角度
[self addChinLine:chinArray descModel:contentArray.lastObject];
//画黄金三角
NSMutableArray *sanjiaoArray = [NSMutableArray arrayWithArray:triangleArray];
[sanjiaoArray addObject:triangleArray.firstObject];
CALayer *sanjiaolayer = [self drawLineMethod:sanjiaoArray Color:[UIColor whiteColor]];
[self.allViewLine addObject:sanjiaolayer];
// 画黄金三角文字
[self creatFourWord:triangleArray addModel:contentArray.firstObject];
// 添加需要移除的控件
[self.allViewLine addObject:self.titleImageView];
[self.allViewLine addObjectsFromArray:self.fristLeftPopView];
[self.fristLeftPopView removeAllObjects];
}
#pragma mark - 眉眼分析
- (void)drawFiveViewAnimation {
// 获取数据, 方便之后使用
NSArray *contnetArray = self.faceModel.analysis.eyeEyebrowInfo.content; // 描述文字数组
NSArray *pointsEye = self.faceModel.analysis.eyeEyebrowInfo.pointsEye; // 眼睛的点坐标
NSArray *pointsEyeBrow = self.faceModel.analysis.eyeEyebrowInfo.pointsEyeBrow; // 眉毛的点坐标
NSArray *subTitle = self.faceModel.analysis.eyeEyebrowInfo.subTitle; // 描述文字
ALFaceRectModel *eyebrowRect = self.faceModel.analysis.eyeEyebrowInfo.eyebrowRect; // 眉毛矩形框
ALFaceRectModel *eyeRect = self.faceModel.analysis.eyeEyebrowInfo.eyeRect; // 眼睛矩形框
GMFaceCommonPositionModel *ptriangle = self.faceModel.analysis.goldTriangleInfo.pointsTriangle[2]; // 获取P点坐标
// 定义缩放比例
CGFloat scale = 1.6;
// 计算P点的新坐标
GMFaceCommonPositionModel *newPoint = [GMFaceCommonPositionModel creatFaceCommonModelWithX:ptriangle.x *scale addY:ptriangle.y *scale];
// 要平移到的最终位置
GMFaceCommonPositionModel *finalPoint = [GMFaceCommonPositionModel creatFaceCommonModelWithX:MAINSCREEN_WIDTH addY:MAINSCREEN_HEIGHT * 0.70];
// 计算便宜量
CGFloat x = finalPoint.x - newPoint.x;
CGFloat y = finalPoint.y - newPoint.y;
// 更改现在的位置
[UIView animateWithDuration:AnimationDuration animations:^{
self.picImageView.frame = CGRectMake(x, y, self.picImageView.frame.size.width * scale, self.picImageView.frame.size.height * scale);
}];
// 计算新的坐标点 并且多加了一个起始点
__weak typeof (self) weakSelf = self;
NSMutableArray *newPointsEyeBrow = [self bigImagePointWithScale:scale offsetX:x offsetY:y poinyArray:pointsEyeBrow];
NSMutableArray *newPointsEye = [self bigImagePointWithScale:scale offsetX:x offsetY:y poinyArray:pointsEye];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(AnimationDuration * 2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (!weakSelf || weakSelf.stopGCD) { return ;}
// 画眉毛
[newPointsEyeBrow addObject:newPointsEyeBrow.firstObject];
CALayer *eyeBrowlayer = [weakSelf drawLineMethod:[newPointsEyeBrow copy] Color:Blue_line];
[weakSelf.allViewLine addObject:eyeBrowlayer];
// 画眼睛
[newPointsEye addObject:newPointsEye.firstObject];
CALayer *eyelayer = [weakSelf drawLineMethod:[newPointsEye copy] Color:Blue_line];
[weakSelf.allViewLine addObject:eyelayer];
});
// 原始坐标点
NSArray *eyebrowMidlePoint = [self midlePointWithModel:eyebrowRect];
NSArray *eyeMidlePoint = [self midlePointWithModel:eyeRect]; // 点坐标
// 计算新点的坐标
NSArray *eyebrowNewPoint = [self bigImagePointWithScale:scale offsetX:x offsetY:y poinyArray:eyebrowMidlePoint];
NSArray *eyeNewPoint = [self bigImagePointWithScale:scale offsetX:x offsetY:y poinyArray:eyeMidlePoint];
// 画竖线
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(AnimationDuration * 3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (!weakSelf || weakSelf.stopGCD) { return ;}
[weakSelf fiveViewdrawlineWithEye:eyeNewPoint eyebrow:eyebrowNewPoint];
});
// 画虚线
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(AnimationDuration * 4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (!weakSelf || weakSelf.stopGCD) { return ;}
[weakSelf fiveViewdrawDottodlineWithEye:eyeNewPoint eyebrow:eyebrowNewPoint modelAry:contnetArray];
});
// 添加描述文字
[self leftPopTitleWith:self.allViewLine AddSubtitle:subTitle Animation:1.5];
}
#pragma mark 新添加页面动画
- (void)drawAddNewAnimation {
// 人中、嘴唇
NSArray *contnetArray = self.faceModel.analysis.renzhongzuichunkediInfo.content;
NSArray *subTitle = self.faceModel.analysis.renzhongzuichunkediInfo.subTitle;
NSArray *renzhongPoints = self.faceModel.analysis.renzhongzuichunkediInfo.renzhongPoints;
NSArray *zuichunPoints = self.faceModel.analysis.renzhongzuichunkediInfo.zuichunPoints;
NSArray *kediPoints = self.faceModel.analysis.renzhongzuichunkediInfo.kediPoints;
// 瞳距鼻翼宽比
NSArray *tjbContnetArray = self.faceModel.analysis.tongjubiyiInfo.content;
NSArray *tjbSubTitle = self.faceModel.analysis.tongjubiyiInfo.subTitle;
NSArray *tongjuPoints = self.faceModel.analysis.tongjubiyiInfo.tongjuPoints;
NSArray *biyiPoints = self.faceModel.analysis.tongjubiyiInfo.biyiPoints;
// 获取中心点
GMFaceCommonPositionModel *tongjuCenterPoints = [self calculateCenterModelWithArray:tongjuPoints];
GMFaceCommonPositionModel *biyiCenterModel = [self calculateCenterModelWithArray:biyiPoints];
// 鼻翼宽度
CGFloat tongjuWidth = [self calculateWidthWithArray:tongjuPoints];
CGFloat biyiWidth = [self calculateWidthWithArray:biyiPoints];
// 添加横线
NSArray<GMFaceCommonPositionModel *> *hArrry = @[tongjuCenterPoints,biyiCenterModel,renzhongPoints[0],renzhongPoints[1],zuichunPoints[1],kediPoints[1]];
NSArray *widthArray = @[@(tongjuWidth),@(biyiWidth),@(100),@(100),@(100),@(100)];
[self drawSixhLine:hArrry widthArray:widthArray];
// 画中间的描述文字
[self creatVerticalDottedLineWith:tjbContnetArray[0] addArray:@[tongjuCenterPoints,biyiCenterModel]offset:0];
[self creatVerticalDottedLineWith:contnetArray[0] addArray:renzhongPoints offset:0];
[self creatVerticalDottedLineWith:contnetArray[1] addArray:zuichunPoints offset:30];
[self creatVerticalDottedLineWith:contnetArray[2] addArray:kediPoints offset:-20];
// 添加描述文字
NSMutableArray *allsubTitle = [NSMutableArray arrayWithArray:tjbSubTitle];
[allsubTitle addObjectsFromArray:subTitle];
// 添加描述文字
[self leftPopTitleWith:self.allViewLine AddSubtitle:allsubTitle Animation:0];
[self.allViewLine addObject:self.titleImageView];
}
#pragma mark - 鼻唇分析
- (void)drawSixViewAnimation {
NSArray *contnetArray = self.faceModel.analysis.mouthNoseInfo.content;
NSArray *subTitle = self.faceModel.analysis.mouthNoseInfo.subTitle;
NSArray *pointsNose = self.faceModel.analysis.mouthNoseInfo.pointsNose;
NSArray *pointsMouth = self.faceModel.analysis.mouthNoseInfo.pointsMouth;
ALFaceRectModel *mouthRect = self.faceModel.analysis.mouthNoseInfo.mouthRect;
// 双C线
NSArray *contentCxian = self.faceModel.analysis.shuangCxianInfo.content;
NSArray *subTitleCxian = self.faceModel.analysis.shuangCxianInfo.subTitle;
NSArray *pointListCxian = self.faceModel.analysis.shuangCxianInfo.pointList;
// 画嘴角线条
for (GMFacePointsMouthInfoModel *mouthModel in pointsMouth) {
CALayer *eyeBrowlayer = [self drawLineMethod:mouthModel.points Color:Blue_line];
[self.allViewLine addObject:eyeBrowlayer];
}
// 画双C线
CALayer *cxianlayer = [self drawLineMethod:pointListCxian Color:Blue_line];
[self.allViewLine addObject:cxianlayer];
NSArray<GMFaceCommonPositionModel *> *rectArray = [self midlePointWithModel:mouthRect];
GMFaceCommonPositionModel* model0 = rectArray[0];
GMFaceCommonPositionModel* model1 = rectArray[1];
GMFaceCommonPositionModel* model2 = rectArray[2];
GMFaceCommonPositionModel* model3 = rectArray[3];
__weak typeof (self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(AnimationDuration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (!weakSelf || weakSelf.stopGCD) { return ;}
// 添加Cxian文字
GMFaceCommonPositionModel *positionModel = pointListCxian[1];
CGRect cxianSize = CGRectMake(positionModel.x + 10, positionModel.y - 15, 100, 45);
GMFaceCommonContentModel *cxianContentModel = contentCxian[0];
GMLabel *dectLabelCxian = [weakSelf creatLabelWithText:[NSString stringWithFormat:@"%@\n%@",cxianContentModel.title,cxianContentModel.value] Font:9 Color:UIColor.whiteColor Size:cxianSize];
dectLabelCxian.alpha = 0.0;
dectLabelCxian.numberOfLines = 2;
dectLabelCxian.textAlignment = NSTextAlignmentLeft;
[weakSelf.allViewLine addObject:dectLabelCxian];
[UIView animateWithDuration:AnimationDuration animations:^{
dectLabelCxian.alpha = 1.0;
}];
// 获取矩形框宽高
CGSize rectSize = [weakSelf getWidthAddHightWithRect:rectArray];
// 画四条竖线
NSArray<GMFaceCommonPositionModel *> *verticalArrry = @[pointsNose[0],pointsNose[1],rectArray[0],rectArray[2]];
NSArray *hightArray = @[@(77),@(77),@(rectSize.height + 30),@(rectSize.height + 30)];
[weakSelf drawSixVerticalLine:verticalArrry hightArray:hightArray];
// 画三条横线
NSArray *realLine = weakSelf.faceModel.analysis.santingInfo.realLinePoints;
NSArray<GMFaceCommonPositionModel *> *hArrry = @[realLine[1],rectArray[1],rectArray[3]];
NSArray *widthArray = @[@(115),@(rectSize.width + 30),@(rectSize.width + 30)];
[weakSelf drawSixhLine:hArrry widthArray:widthArray];
// 画带箭头的线条
[weakSelf drawDottodLineWithPointArray:pointsNose content:contnetArray[0] type:drawViewTypeHorizontalDotted place:wordPlaceTop];
GMFaceCommonPositionModel* newModel0 = model1;
newModel0.x = model2.x;
NSArray *rightArray = @[newModel0, model3];
[weakSelf drawDottodLineWithPointArray:rightArray content:contnetArray[1] type:drawViewTypeVerticalDotted place:wordPlaceRight];
GMFaceCommonPositionModel* newModel1 = model0;
newModel1.y = model3.y + 15;
NSArray *bootmArray = @[newModel1, model2];
[weakSelf drawDottodLineWithPointArray:bootmArray content:contnetArray[2] type:drawViewTypeHorizontalDotted place:wordPlaceBottom];
});
// 添加描述文字
NSMutableArray *allsubTitle = [NSMutableArray arrayWithArray:subTitle];
[allsubTitle addObjectsFromArray:subTitleCxian];
[self leftPopTitleWith:self.allViewLine AddSubtitle:subTitle Animation:0.5];
}
// 返回方法!
- (void)backMethod {
// 返回到具体页面
self.stopGCD = YES;
BOOL isPop = YES;
for (UIViewController *vc in AppDelegate.navigation.childViewControllers) {
if ([vc isKindOfClass:[GMFaceInfoShareViewController class]]){
isPop = NO;
[AppDelegate.navigation popToViewController:vc animated:YES];
return;
}
}
if (isPop) {
[AppDelegate.navigation popToRootViewControllerAnimated:YES];
}
}
#pragma mark - 代理方法
// 开启录屏时发现未知错误 -- 文字提醒
-(void)didStartRecordWithErrorString:(NSString *)errorString{
[self showWarning:errorString];
// 返回到上一页
__weak typeof(self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[weakSelf backMethod];
});
}
-(NSMutableArray *)fristLeftPopView{
if (!_fristLeftPopView) {
_fristLeftPopView = [NSMutableArray array];
}
return _fristLeftPopView;
}
@end
//
// GMScanAnimationController.h
// Gengmei
//
// Created by 刘鹿杰 on 2019/6/19.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@interface GMScanAnimationController : WMBaseViewController
@property (nonatomic, strong) UIImage *image;
@end
NS_ASSUME_NONNULL_END
//
// GMScanAnimationController.m
// Gengmei
//
// Created by 刘鹿杰 on 2019/6/19.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMScanAnimationController.h"
#import "ALFaceInfoViewModel.h"
#import "UIImage+GMExtension.h"
#import "GMFaceV3Util.h"
@interface GMScanAnimationController ()
@end
@implementation GMScanAnimationController
-(void)initController{
[super initController];
self.navigationBar.backgroundColor = UIColor.clearColor;
self.navigationBar.titleLabel.textColor = UIColor.whiteColor;
self.navigationBar.leftIcon = @"NewFaceBack";
self.navigationBar.isShowShadow = NO;
}
- (void)viewDidLoad {
[super viewDidLoad];
UIImage *newImage = [self.image normalizedImage]; // 防止iOS 13 图片自动旋转90度
GMImageView *imageView = [[GMImageView alloc] init];
imageView.contentMode = UIViewContentModeScaleAspectFill;
imageView.frame = self.view.frame;
imageView.image = newImage;
[self.view addSubview:imageView];
GMImageView *line = [[GMImageView alloc] initWithFrame:CGRectMake(0, -162, MAINSCREEN_WIDTH, 162)];
line.backgroundColor = [UIColor blueColor];
line.tag = 1111;
line.image = [UIImage imageNamed:@"icon_face_scanning_down"];
line.contentMode = UIViewContentModeScaleAspectFill;
line.backgroundColor = [UIColor clearColor];
[self.view addSubview:line];
[self addAnimation];
[GMFaceV3Util uploadOneImageToQiNiu:newImage vc:self successBlock:NULL];
}
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
[self removeAnimation];
}
// 添加扫描动画
- (void)addAnimation{
UIView *line = [self.view viewWithTag:1111];
line.hidden = NO;
CABasicAnimation *animation = [GMScanAnimationController moveYTime:2.5 fromY:[NSNumber numberWithFloat:0] toY:[NSNumber numberWithFloat:MAINSCREEN_HEIGHT] rep:OPEN_MAX];
[line.layer addAnimation:animation forKey:@"LineAnimation"];
}
// 去除扫描动画
- (void)removeAnimation{
UIView *line = [self.view viewWithTag:1111];
[line.layer removeAnimationForKey:@"LineAnimation"];
line.hidden = YES;
}
+ (CABasicAnimation *)moveYTime:(float)time fromY:(NSNumber *)fromY toY:(NSNumber *)toY rep:(int)rep{
CABasicAnimation *animationMove = [CABasicAnimation animationWithKeyPath:@"transform.translation.y"];
[animationMove setFromValue:fromY];
[animationMove setToValue:toY];
animationMove.duration = time;
animationMove.repeatCount = rep;
animationMove.fillMode = kCAFillModeForwards;
animationMove.removedOnCompletion = YES;
animationMove.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
return animationMove;
}
@end
//
// GMTestSkinAnimationWebView.h
// Gengmei
//
// Created by 刘鹿杰 on 2019/8/21.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@interface GMTestSkinAnimationWebView : WMBaseViewController
// 需要上传到七牛的图片
@property (nonatomic, strong) UIImage *image;
@end
NS_ASSUME_NONNULL_END
//
// GMTestSkinAnimationWebView.m
// Gengmei
//
// Created by 刘鹿杰 on 2019/8/21.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMTestSkinAnimationWebView.h"
#import "GMFaceV3Util.h"
#import "Gengmei-Swift.h"
#import "UIView+Gradient.h"
#import "GMPhotoDefine.h"
#import "ALShareSheet.h"
#import "GMSkinUpLoadHeadPicPopView.h"
#import "Lottie.h"
#import "GMAIFacePopViewObject.h"
#import "GMFacePhotosPopView.h"
#define ShareImageHeight (MAINSCREEN_HEIGHT - UIView.safeAreaInsetsBottom - 206 - self.navigationBar.bottom)
@interface GMTestSkinAnimationWebView ()<WKWebViewDelegate,GMPhotoPickDismissDelegate>
@property (nonatomic, strong) GMImageView *bgImageView; // 待展示图片的image
@property (nonatomic, strong) GMView *loadingView; // 动画View
@property (nonatomic, strong) GMView *contentView;
@property (nonatomic, strong) UIView *bottomView; // 悬浮按钮View
@property (nonatomic, strong) GMWebViewComponent *webCompent; // WebView
@property (nonatomic, strong) NSDictionary *dataDict;
@property (nonatomic, strong) UIImage *shareImage; // 前端传递过来的图片
@property (nonatomic, strong) GMImageView *shareImageView; // 分享展示的图片
@property (nonatomic, strong) ALShareSheet *shareSheet; // 分享弹出框
@property (nonatomic, copy) NSString *imUrl;
@property (nonatomic, assign) BOOL webLoadSucceed;
@property (nonatomic, copy) NSString *oringImageURL; // 上传的图片链接
@property (nonatomic, strong) GMAIFacePopViewObject *popViewObject; // 弹框模型
@property (nonatomic, strong) GMFacePhotosPopView *popView; // 弹出提示框
@end
@implementation GMTestSkinAnimationWebView
- (void)initController {
[super initController];
self.pageName = @"face_detect_result";
self.navigationBar.backgroundColor = UIColor.clearColor;
}
-(void)viewDidLoad {
[super viewDidLoad];
self.navigationBar.isShowShadow = NO;
self.fd_interactivePopDisabled = YES;
// 添加上次报告数据浏览
NSString *haveLastReport = [GMCache fetchObjectAtDocumentPathWithkey:@"GMTestSkinIsShowData"];
NSDictionary *responseDict = [GMCache fetchObjectAtDocumentPathWithkey:@"GMTestSkinLastData"];
BOOL haveScanSkin = GMLaunchManager.shareManager.appConfigObject.haveScanSkin; // 查看远端是否有上次报告
self.oringImageURL = [GMCache fetchObjectAtDocumentPathWithkey:@"GMLastReportOringImageURL"]; // 查看是否有图片链接
[self.view addSubview:self.bgImageView];
[self.view addSubview:self.loadingView];
// 添加webView
self.webCompent = [[GMWebViewComponent alloc] init];
_webCompent.delegate = self;
[self.view insertSubview:_webCompent belowSubview:self.bgImageView];
[_webCompent mas_updateConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(0);
make.top.mas_equalTo(OCNavigationBar.barHeight);
}];
if (haveLastReport.isNonEmpty && responseDict && !self.image) { // 本地数据存储
[self successDeal:responseDict];
} else if (haveScanSkin && !self.image){ // 加载上次报告网络数据
__weak typeof(self) weakSelf = self;
[GMNetworking requestOCWithApi:API_FACE_GET_SKIN_RECORD method:GMHTTPMethodGet parameters:nil completion:^(GMResponseOC * _Nonnull response) {
if (response.isSuccess){
[GMCache storeObjectAtDocumentPathWithkey:@"GMTestSkinLastData" object:response.data];
[GMCache storeObjectAtDocumentPathWithkey:@"GMTestSkinIsShowData" object:@"yes"];
[weakSelf successDeal:response.data];
}else{
[weakSelf toast:response.data[@"资源阻塞, 请稍后再试!"]];
[weakSelf.navigationController popViewControllerAnimated:YES];
}
}];
} else { // 测肤正常进入的时候, 走这个分支
__weak typeof(self) weakSelf = self;
[GMFaceV3Util uploadImageToQiNiu:self.image successBlock:^(id responseObject) {
weakSelf.oringImageURL = [NSString stringWithFormat:@"https://pic.igengmei.com/%@-aspectscale800", responseObject];
NSDictionary *param = @{@"image_url":SafeString(weakSelf.oringImageURL)};
[GMNetworking requestOCWithApi:API_FACE_APP_TEST_SKIN method:GMHTTPMethodGet parameters:param completion:^(GMResponseOC * _Nonnull response) {
if (response.isSuccess){
[GMCache storeObjectAtDocumentPathWithkey:@"GMTestSkinLastData" object:response.data];
[GMCache storeObjectAtDocumentPathWithkey:@"GMTestSkinIsShowData" object: @"yes"];
[GMCache storeObjectAtDocumentPathWithkey:@"GMLastReportOringImageURL" object: weakSelf.oringImageURL];
[weakSelf successDeal:response.data];
}else{
[weakSelf toast:response.data[@"资源阻塞, 请稍后再试!"]];
[weakSelf.navigationController popViewControllerAnimated:YES];
}
}];
}];
}
// 加载loading页面弹框数据
[self loadPopViewData];
}
-(void)successDeal:(NSDictionary *)dict{
if ([dict[@"thrid_consultation_url"] isNonEmpty]) {
self.imUrl = dict[@"thrid_consultation_url"];
}
if ([dict[@"retcode"] integerValue] == 0) {
self.dataDict = dict;
[self.webCompent setLocalStorage:@"skinUserImg" value:SafeString(self.oringImageURL)];
self.webCompent.fullUrl = [NSString stringWithFormat:@"%@/%@",GMServerDomains.apiHost, SafeString(dict[@"gray_url"] ?: @"phantom/skin_measuring_report_3")];
[self.webCompent webviewLoad:self];
}else{
[Phobos track:@"popup_view" attributes:@{@"page_name" : self.pageName,
@"popup_name" : SafeValue(dict[@"retcode"]),@"popup_content" : SafeValue(dict[@"retmsg"])}];
[self toast:dict[@"retmsg"]];
[self.navigationController popViewControllerAnimated:YES];
}
}
- (void)loadPopViewData {
// 加载弹框逻辑:
[GMNetworking requestOCWithApi:@"/gm_ai/pop_window" method:GMHTTPMethodGet parameters:@{@"pop_type": @"2"} completion:^(GMResponseOC * _Nonnull response) {
if (response.isSuccess) { // 加载成功
self.popViewObject = [[GMAIFacePopViewObject alloc] initWithDictionary:response.data error:nil];
[self.navigationController.view addSubview:self.popView]; // 添加弹出框
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[Phobos track:@"popup_view" attributes:@{@"page_name" : SafeString(self.pageName), @"popup_name" :SafeString(self.popViewObject.title),@"popup_content" : SafeString(self.popViewObject.content)}];
[UIView animateWithDuration:0.25 animations:^{
self.popView.mj_y = (0);
} completion:^(BOOL finished) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[UIView animateWithDuration:0.25 animations:^{
self.popView.mj_y = -(100 + OCNavigationBar.statusBarHeight);
} completion:^(BOOL finished) {
[self.popView removeFromSuperview];
}];
});
}];
});
}
}];
}
// leftButton点击事件
-(void)backAction:(OCNavigationBarButton *)button{
if (self.webLoadSucceed){
[self.webCompent.webView evaluateJavaScript:@"window.gm.pack.run('signBack')" completionHandler:nil];
}else{
[self.navigationController popViewControllerAnimated:YES];
}
}
- (void)rightButtonClicked:(OCNavigationBarButton *)button {
[self shareFriend];
}
// 再测一次
-(void)againBtnClick {
[Phobos track:@"on_click_button" attributes:@{@"page_name" : self.pageName, @"button_name" : @"detect_again" }];
if ([self.referer isEqualToString:@"home"]) {
[[GMRouter sharedInstance] pushScheme:@"gengmei://scan_faceimage?face_skin_tab_index=1"];
}else{
[self.navigationController popViewControllerAnimated:YES];
}
}
#pragma mark - 添加悬浮btn 及逻辑处理
- (void)addSuspendBottomView {
CGFloat margin = (MAINSCREEN_WIDTH - 103 * 3) / 4;
// 专家咨询
GMButton *consultBtn = [self creatBtnWithImageName:@"face_photo_bgImage" title:@"专家咨询" titleColor:RGBCOLOR_HEX(0xFF5963) selector:@selector(imButtonClick)];
[consultBtn setImage:[UIImage imageNamed:@"face_test_consultIcon"] forState:UIControlStateNormal];
[self.bottomView addSubview:consultBtn];
[consultBtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(38);
make.width.mas_equalTo(103);
make.right.mas_equalTo(self.view.mas_right).offset(-margin);
make.bottom.mas_equalTo(self.view.mas_bottom).offset(btnBottomMargin);
}];
// 颜值分析
GMButton *skinAnalyseBtn = [self creatBtnWithImageName:@"face_analyse_bgImage" title:@"颜值分析" titleColor:UIColor.auxiliaryTextGreen selector:@selector(aiButtonClick)];
[skinAnalyseBtn setImage:[UIImage imageNamed:@"face_test_analyse"] forState:UIControlStateNormal];
[self.bottomView addSubview:skinAnalyseBtn];
[skinAnalyseBtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(38);
make.width.mas_equalTo(103);
make.centerX.mas_equalTo(self.view.mas_centerX);
make.bottom.mas_equalTo(self.view.mas_bottom).offset(btnBottomMargin);
}];
// 再测一次
GMButton *againBtn = [self creatBtnWithImageName:@"face_analyse_bgImage" title:@"再测一次" titleColor:UIColor.auxiliaryTextGreen selector:@selector(againBtnClick)];
[againBtn setImage:[UIImage imageNamed:@"face_test_photoIcon"] forState:UIControlStateNormal];
[self.bottomView addSubview:againBtn];
[againBtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(38);
make.width.mas_equalTo(103);
make.left.mas_equalTo(self.view.mas_left).offset(margin);
make.bottom.mas_equalTo(self.view.mas_bottom).offset(btnBottomMargin);
}];
}
- (GMButton *)creatBtnWithImageName:(NSString *)imageStr title:(NSString *)title titleColor:(UIColor *)titleColor selector:(SEL)sel{
GMButton *btn = [[GMButton alloc] init];
btn.adjustsImageWhenHighlighted = NO;
btn.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 5);
btn.titleEdgeInsets = UIEdgeInsetsMake(0, 5, 0, 0);
[btn setBackgroundImage:[UIImage imageNamed:imageStr] forState:UIControlStateNormal];
[btn setTitle:title forState:UIControlStateNormal];
[btn setTitleColor:titleColor forState:UIControlStateNormal];
[btn.titleLabel setFont:[UIFont systemFontOfSize:14]];
[btn addTarget:self action:sel forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
return btn;
}
- (void)imButtonClick {
if (self.imUrl) {
[Phobos track:@"on_click_button" attributes:@{@"page_name" : self.pageName, @"button_name" : @"im_button" }];
[[GMRouter sharedInstance] pushScheme:self.imUrl];
}
}
-(void)goHomeBtnClick {
[Phobos track:@"on_click_button" attributes:@{@"page_name" :SafeString(self.pageName), @"button_name" : @"back_home"}];
[self.webCompent.webView evaluateJavaScript:@"window.gm.pack.run('signBack','sk_hm')" completionHandler:nil];
}
- (void)aiButtonClick {
// 发送通知,切换摄像头
[Phobos track:@"on_click_button" attributes:@{@"page_name" : self.pageName, @"button_name" : @"ai_function" }];
if ([self.referer isEqualToString:@"home"]){
[[GMRouter sharedInstance] pushScheme:@"gengmei://scan_faceimage"];
}else{
[[NSNotificationCenter defaultCenter] postNotificationName:@"GMTestSkinAnimationWebViewSenBack" object:nil];
[self.navigationController popViewControllerAnimated:YES];
}
}
- (void)shareFriend {
[Phobos track:@"page_click_share" attributes:@{@"page_name" : self.pageName}];
[self.webCompent.webView evaluateJavaScript:@"window.gm.pack.run('share')" completionHandler:nil];
}
- (void)changeAvatar {
GMPhotoPickController *controller = [[GMPhotoPickController alloc] initWithMaxImageCount:1 maxVideoCount:0 maxCount:1 photoDisplayType:GMPhotosDisplayImageType];
controller.dismissDelegate = self;
controller.root.singleSelection = YES;
controller.root.showCropFirst = YES;
[self presentViewController:controller animated:YES completion:NULL];
}
// 获取到截取的图片
- (void)dismissPhotoPick:(NSArray<GMEditPhotoInfo *> *)infos {
if (infos.count == 0) { return;}
GMEditPhotoInfo *model = infos.firstObject;
UIImage *image = model.finshedImage;
[GMFaceV3Util uploadImageToQiNiu:image successBlock:^(id responseObject) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSString *urlStr = [NSString stringWithFormat:@"https://pic.igengmei.com/%@-aspectscale800", responseObject];
NSString *javaScriptStr = [NSString stringWithFormat:@"window.gm.pack.run('qnIconKey','%@')",urlStr];
[self.webCompent.webView evaluateJavaScript:javaScriptStr completionHandler:nil];
});
}];
}
#pragma mark - 前端与移动端连调方法
-(void)jsControlBackToMethod:(NSString *)str{
if([str isEqualToString:@"pop"]){ // 返回测肤页面
[self.navigationController popViewControllerAnimated:YES];
}else if([str isEqualToString:@"face"]){ // 返回测脸页面
if ([self.referer isEqualToString:@"home"]){
// [[GMRouter sharedInstance] pushScheme:@"gengmei://scan_faceimage"];
[[GMRouter sharedInstance] pushScheme:@"gengmei://scan_faceimage"];
}else{
[[NSNotificationCenter defaultCenter] postNotificationName:@"GMTestSkinAnimationWebViewSenBack" object:nil];
[self.navigationController popViewControllerAnimated:YES];
}
}
}
// 判断按钮滑动的位置
-(void)jsPageReachBottom:(BOOL)isScrollbottom{}
- (NSString *)getConvertWithKey:(NSString *)key {
return [NSString convertToPrettyPrintedJsonString:self.dataDict[key] ?: @{}];
}
-(void)globalDataLoaded:(NSDictionary *)data{
// 调取前端方法
NSString *dictStr = [NSString stringWithFormat:@"%@,%@,%@,%@", [self getConvertWithKey:@"skin_check_results"], [self getConvertWithKey:@"own_diagnose"], [self getConvertWithKey:@"current_user"], [self getConvertWithKey:@"popup"]];
NSString *newDictStr = [[dictStr stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] stringByReplacingOccurrencesOfString:@"\n" withString:@""];
NSString *javaScriptStr = [NSString stringWithFormat:@"window.gm.pack.run('jsPageRenderData',%@)",newDictStr];
[self.webCompent.webView evaluateJavaScript:javaScriptStr completionHandler:nil];
[UIView animateWithDuration:0.25 animations:^{
self.bgImageView.alpha = 0.0;
self.loadingView.alpha = 0.0;
self.navigationBar.backgroundColor = UIColor.whiteColor;
self.navigationBar.title = @"肤质报告";
self.navigationBar.rightIcon = @"face_right_share_image";
[self addSuspendBottomView]; // 添加悬浮按钮
self.webLoadSucceed = YES;
} completion:^(BOOL finished) {
self.bgImageView.hidden = YES;
[self.loadingView removeFromSuperview];
}];
}
// 当图片生成时,前端会调用这个方法
- (void)shareSkinInfoImageMethod:(NSString *)imageDataStr{
_shareImage = [self stringToImageWith:imageDataStr imageStr:@"imageString"];
if (!_shareImage) {
[self toast:@"糟糕,生成失败了,再试一次吧"];
return;
}
// 展示分享框
[self shareFaceInfo];
// 保存图片到相册
UIImageWriteToSavedPhotosAlbum(_shareImage, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);
[self toast:@"已保存至相册"];
}
- (void)shareFaceInfo {
CGFloat scale1 = _shareImage.size.height / _shareImage.size.width;
CGFloat width1 = ShareImageHeight / scale1;
self.shareImageView.image = self.shareImage;
self.shareImageView.frame = CGRectMake((MAINSCREEN_WIDTH - width1)/2, OCNavigationBar.barHeight + 10, width1, ShareImageHeight);
[self.shareSheet addSubview:self.shareImageView];
[self.shareSheet show];
[UIView animateWithDuration:0.5 animations:^{
_shareImageView.alpha = 1.0;
}];
}
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo {}
// 解析前端传递过来的图片
- (UIImage *)stringToImageWith:(NSString *)imageData imageStr:(NSString *)imageStr {
NSData *jsonData = [imageData dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:nil];
NSString *imageString = dict[imageStr] ?: @"";
UIImage *image = [UIImage imageWithData:[[NSData alloc] initWithBase64EncodedString:imageString options:NSDataBase64DecodingIgnoreUnknownCharacters]];
return image;
}
#pragma mark - 懒加载数据
- (ALShareSheet *)shareSheet {
if (!_shareSheet) {
_shareSheet = [[ALShareSheet alloc] init];
_shareSheet.bgAlphaColor = RGBCOLOR_HEX(0xF4F3F8);
__weak typeof(self) weakSelf = self;
_shareSheet.hiddenBlock = ^{ // 隐藏
[UIView animateWithDuration:0.25 animations:^{
weakSelf.shareImageView.alpha = 0.0;
} completion:^(BOOL finished) {
[weakSelf.shareImageView removeFromSuperview];
}];
};
_shareSheet.shareTypeBlock = ^(GMSharePlatform platform) { // 分享
[[GMShareSDK shareInstance] shareImage:weakSelf.shareImage platform:platform];
[Phobos track:@"page_click_share_channel" attributes:@{@"page_name":SafeString(weakSelf.pageName),
@"share_channel":SafeString([GMShareSDK shareChannel:platform])}];
};
}
return _shareSheet;
}
- (GMImageView *)bgImageView {
if (!_bgImageView) {
_bgImageView = [[GMImageView alloc] initWithFrame:CGRectMake(0, 0, MAINSCREEN_WIDTH, MAINSCREEN_HEIGHT)];
if (self.image) {
_bgImageView.image = self.image;
}else if (_oringImageURL.isNonEmpty){
[_bgImageView sd_setImageWithURL:[NSURL URLWithString:self.oringImageURL]];
}else {
_bgImageView.image = [UIImage imageNamed:@"facev5_share_bgImage"];
}
_bgImageView.contentMode = UIViewContentModeScaleAspectFill;
}
return _bgImageView;
}
- (GMView *)loadingView {
if (!_loadingView) {
_loadingView = [[GMView alloc] initWithFrame:CGRectMake(0, 0, MAINSCREEN_WIDTH, MAINSCREEN_HEIGHT)];
_loadingView.backgroundColor = [UIColor colorWithHex:0 alpha:0.8];
GMImageView *loadingImageView = [[GMImageView alloc] initWithFrame:CGRectMake((MAINSCREEN_WIDTH - 300) / 2, (MAINSCREEN_HEIGHT - 300) / 2, 300, 300)];
NSString *path = [[NSBundle mainBundle] pathForResource:@"skinloadinganimation.webp" ofType:nil];
NSData *data = [NSData dataWithContentsOfFile:path];
loadingImageView.image = [UIImage sd_imageWithData:data];
[_loadingView addSubview:loadingImageView];
}
return _loadingView;
}
- (GMImageView *)shareImageView {
if (!_shareImageView) {
_shareImageView = [[GMImageView alloc] init];
_shareImageView.frame = CGRectMake(0, 0, MAINSCREEN_WIDTH, MAINSCREEN_HEIGHT);
_shareImageView.contentMode = UIViewContentModeScaleAspectFill;
}
return _shareImageView;
}
- (GMFacePhotosPopView *)popView {
if (!_popView) {
_popView = [[GMFacePhotosPopView alloc] init];
CGFloat popViewH = 100 + OCNavigationBar.statusBarHeight;
_popView.frame = CGRectMake(0, -popViewH, MAINSCREEN_WIDTH, popViewH);
_popView.popModel = self.popViewObject;
// 剪切圆角
[self changeLabelStyle:_popView];
}
return _popView;
}
- (void)changeLabelStyle:(GMView *)view {
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:view.bounds byRoundingCorners:UIRectCornerBottomLeft | UIRectCornerBottomRight cornerRadii:CGSizeMake(10, 10)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = view.bounds;
maskLayer.path = maskPath.CGPath;
view.layer.mask = maskLayer;
}
-(UIView *)bottomView {
if (!_bottomView) {
_bottomView = [[UIView alloc] init];
_bottomView.backgroundColor = [UIColor clearColor];
[self.view insertSubview:_bottomView belowSubview:self.bgImageView];
[_bottomView mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(84 + UIView.safeAreaInsetsBottom);
make.left.right.mas_equalTo(0);
make.bottom.mas_equalTo(self.view.mas_bottom);
}];
// 添加背景颜色
GMImageView *bgImgView = [[GMImageView alloc] initWithImage:[UIImage imageNamed:@"face_bottomView_bgImage"]];
[_bottomView addSubview:bgImgView];
[bgImgView mas_updateConstraints:^(MASConstraintMaker *make) {
make.left.right.top.mas_equalTo(0);
make.height.mas_equalTo(84);
}];
// 安全区域防止透明
UIView *safeAreaView = [[UIView alloc] init];
safeAreaView.backgroundColor = [UIColor whiteColor];
[_bottomView addSubview:safeAreaView];
[safeAreaView mas_updateConstraints:^(MASConstraintMaker *make) {
make.left.right.bottom.mas_equalTo(0);
make.height.mas_equalTo(UIView.safeAreaInsetsBottom);
}];
}
return _bottomView;
}
@end
//
// GMImageView+GMFace.h
// Gengmei
//
// Created by 刘鹿杰 on 2019/6/26.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@interface GMImageView (GMFace)
// 给定 X 轴, 画虚 - 竖线
+ (GMImageView *)creatVerticalDottedWithX:(CGFloat)X;
// 给定 X 轴, 画实 - 竖线
+ (GMImageView *)creatVerticalPathWithX:(CGFloat)X;
// 给定 Y 轴, 画虚 - 横线
+ (GMImageView *)creatHorizontalDottedWithY:(CGFloat)Y;
// 给定 Y 轴, 画实 - 横线
+ (GMImageView *)creatHorizontalPathWithY:(CGFloat)Y;
@end
NS_ASSUME_NONNULL_END
//
// GMImageView+GMFace.m
// Gengmei
//
// Created by 刘鹿杰 on 2019/6/26.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMImageView+GMFace.h"
#import "UIImage+GMExtension.h"
#import "UIView+GMExtension.h"
@implementation GMImageView (GMFace)
// 给定 X 轴, 画虚 - 竖线
+ (GMImageView *)creatVerticalDottedWithX:(CGFloat)X {
UIImage *image = [UIImage imageNamed:@"facev3_vertical_dotted"];
GMImageView *imageView = [[GMImageView alloc] initWithImage:[image resizableImage]];
imageView.gm_x = X;
imageView.size = CGSizeMake(1, 0);
return imageView;
}
// 给定 X 轴, 画实 - 竖线
+ (GMImageView *)creatVerticalPathWithX:(CGFloat)X {
UIImage *image = [UIImage imageNamed:@"facev3_vertical_path"];
GMImageView *imageView = [[GMImageView alloc] initWithImage:[image resizableImage]];
imageView.gm_x = X;
imageView.size = CGSizeMake(1, 0);
return imageView;
}
// 给定 Y 轴, 画虚 - 横线
+ (GMImageView *)creatHorizontalDottedWithY:(CGFloat)Y {
UIImage *image = [UIImage imageNamed:@"facev3_horizontal_dotted"];
GMImageView *imageView = [[GMImageView alloc] initWithImage:[image resizableImage]];
imageView.gm_y = Y;
imageView.size = CGSizeMake(0, 1);
return imageView;
}
// 给定 Y 轴, 画实 - 横线
+ (GMImageView *)creatHorizontalPathWithY:(CGFloat)Y {
UIImage *image = [UIImage imageNamed:@"facev3_horizontal_path"];
GMImageView *imageView = [[GMImageView alloc] initWithImage:[image resizableImage]];
imageView.gm_y = Y;
imageView.size = CGSizeMake(0, 1);
return imageView;
}
@end
//
// GMNewFaceTool.h
// Gengmei
//
// Created by 叶凤鸣 on 2019/6/19.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
typedef void(^NewPhotoCallBack)(UIImage *image);
@interface GMNewFaceTool : NSObject
+ (void)latestImage:(NewPhotoCallBack)callBack;
@end
//
// GMNewFaceTool.m
// Gengmei
//
// Created by 叶凤鸣 on 2019/6/19.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMNewFaceTool.h"
#import <Photos/Photos.h>
@implementation GMNewFaceTool
+ (void)latestImage:(NewPhotoCallBack)callBack {
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
if (status == PHAuthorizationStatusAuthorized) {
// PHAsset *asset = [PHAsset latestAsset];
PHFetchOptions *options = [[PHFetchOptions alloc] init];
options.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]];
PHFetchResult *assetsFetchResults = [PHAsset fetchAssetsWithOptions:options];
PHAsset *asset = [assetsFetchResults firstObject];
// 在资源的集合中获取第一个集合,并获取其中的图片
if (asset) {
PHCachingImageManager *imageManager = [[PHCachingImageManager alloc] init];
[imageManager requestImageDataForAsset:asset options:nil resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) {
UIImage *image = nil;
if (imageData) {
image = [UIImage imageWithData:imageData];
}
if (callBack) {
callBack(image);
}
}];
} else {
if (callBack) {
callBack(nil);
}
}
} else {
if (callBack) {
callBack(nil);
}
}
}];
}
@end
//
// GMScreenRecorderManager.h
// LJRepayer
//
// Created by 刘鹿杰 on 2019/7/30.
// Copyright © 2019 刘鹿杰. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <ReplayKit/ReplayKit.h>
typedef void(^StartRecordSuccess)();
NS_ASSUME_NONNULL_BEGIN
@protocol GMScreenRecorderDelegate <NSObject>
@optional
// 开启录屏失败时,回调信息
- (void)didStartRecordWithErrorString:(NSString *)errorString;
// 停止录屏失败时 回调信息
- (void)didStopRecordWithErrorString:(NSString *)errorString;
// 视频保存成功回调
- (void)saveVideoSuscessWithAssetURl:(AVURLAsset *)assetURL;
@end
@interface GMScreenRecorderManager : NSObject
+ (instancetype)sharedManager;
@property(nonatomic, weak)id<GMScreenRecorderDelegate> delegate;
// 开始录屏是否需要启用麦克风
+ (BOOL)startRecord:(BOOL)isNeedMicroPhone suscess:(StartRecordSuccess)suscess;
// 结束录屏,并保存视频到相册
+ (void)stopRecordWithSave;
// 结束录屏,丢弃这次的录制
+ (void)stopRecordWithDiscard;
//合成视频
- (void)mixAudioAndVidoWithInputURL2:(NSURL*)inputURL outPath:(NSString *)outPath;
@end
NS_ASSUME_NONNULL_END
//
// GMScreenRecorderManager.m
// LJRepayer
//
// Created by 刘鹿杰 on 2019/7/30.
// Copyright © 2019 刘鹿杰. All rights reserved.
//
#import "GMScreenRecorderManager.h"
#import "RPPreviewViewController+MovieURL.h"
#import <Photos/Photos.h>
#import "GMHudModule+Alpha.h"
static GMScreenRecorderManager *sharedManager = nil;
@interface GMScreenRecorderManager()<RPScreenRecorderDelegate>
@property (assign, nonatomic) BOOL isRecord;
@end
@implementation GMScreenRecorderManager
// 初始化方法
- (instancetype)init{
if (self = [super init]) {
_isRecord = NO;
[RPScreenRecorder sharedRecorder].delegate = self;
}
return self;
}
// 单例创建对象
+ (instancetype)sharedManager {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedManager = [[GMScreenRecorderManager alloc] init];
});
return sharedManager;
}
// 开始录屏
+ (BOOL)startRecord:(BOOL)isNeedMicroPhone suscess:(StartRecordSuccess)suscess {
return [[self sharedManager] start:isNeedMicroPhone suscess:suscess];
}
// 结束录屏,并保存视频到相册
+ (void)stopRecordWithSave{
[[self sharedManager] stopAndSave];
}
// 结束录屏,丢弃这次的录制
+ (void)stopRecordWithDiscard{
[[self sharedManager] stopRecord];
}
// 是否正在录制视频
- (BOOL)isRecording {
if (RPScreenRecorder.sharedRecorder.isRecording || [self isRecord]) { // 正在录制 Or 或者硬件不可以用
[self showStartRecordErrorWithText:@"正在录屏中,请稍后再试!"];
return NO;
}
return YES;
}
// 检验设备是否支持 ReplayKit
- (BOOL)replayKitIsAvailable {
if(!RPScreenRecorder.sharedRecorder.isAvailable) {
[self showStartRecordErrorWithText:@"当前设备不支持录屏功能,请升级您的系统版本!"];
return NO;
}
return YES;
}
// 开始录制
- (BOOL)start:(BOOL)isNeedMicroPhone suscess:(StartRecordSuccess)suscess {
__weak typeof(self) weakSelf = self;
if ([self isRecording] && [self replayKitIsAvailable]) {
if (@available(iOS 10.2, *)) {
RPScreenRecorder.sharedRecorder.microphoneEnabled = NO;
[RPScreenRecorder.sharedRecorder startRecordingWithHandler:^(NSError * _Nullable error) {
if (!error) {
weakSelf.isRecord = YES;
dispatch_async(dispatch_get_main_queue(), ^{
suscess();
});
}else{
weakSelf.isRecord = NO;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf showStartRecordErrorWithText:@"录屏开启失败,请重启手机后再试!"];
});
}
}];
}else{
[self showStartRecordErrorWithText:@"系统版本过低,录屏未能成功开启!"];
}
}
return YES;
}
// 结束录屏,丢弃这次的录制
-(void)stopRecord {
__weak typeof(self) weakSelf = self;
if (RPScreenRecorder.sharedRecorder.isRecording) {
[[RPScreenRecorder sharedRecorder] stopRecordingWithHandler:^(RPPreviewViewController * _Nullable previewViewController, NSError * _Nullable error) {
weakSelf.isRecord = NO;
if (!error) {
[[RPScreenRecorder sharedRecorder] discardRecordingWithHandler:^{}];
}
}];
}
}
// 结束录屏,并保存视频到相册
-(void)stopAndSave {
__weak typeof(self) weakSelf = self;
if (RPScreenRecorder.sharedRecorder.isRecording) {
[[RPScreenRecorder sharedRecorder] stopRecordingWithHandler:^(RPPreviewViewController * _Nullable previewViewController, NSError * _Nullable error) {
weakSelf.isRecord = NO;
if (!error) {
if ([previewViewController respondsToSelector:@selector(movieURL)]) {
NSURL *videoURL = [previewViewController.movieURL copy];
if (videoURL) {
dispatch_async(dispatch_get_main_queue(), ^{
[[GMScreenRecorderManager sharedManager] mixAudioAndVidoWithInputURL2:videoURL outPath:[weakSelf getVidoesPathStr]];
});
}
}else{
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf showWarningWithText:@"获取不到视频连接,请升级手机系统后再试一次!"];
});
}
}else{
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf showWarningWithText:@"停止录屏失败,请您重启手机再试!"];
});
}
}];
}else{
[weakSelf showWarningWithText:@"录屏期间退出屏幕,录制文件已经被丢弃!"];
}
}
// 获取输出路经
-(NSString *)getVidoesPathStr{
NSDate* date = [NSDate dateWithTimeIntervalSinceNow:0];
NSTimeInterval time = [date timeIntervalSince1970] * 1000;
NSString *timeString = [NSString stringWithFormat:@"%.0f", time];
NSString *fileName = [NSString stringWithFormat:@"%@_%@",@"tmp",timeString];
NSString *outPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:[fileName stringByAppendingString:@".mp4"]];
return outPath;
}
+ (BOOL)detectionPhotoState:(void(^)(void))authorizedResultBlock {
BOOL isAvalible = NO;
PHAuthorizationStatus authStatus = [PHPhotoLibrary authorizationStatus];
if (authStatus == PHAuthorizationStatusNotDetermined) {
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
if (status == PHAuthorizationStatusAuthorized)
{
if (authorizedResultBlock)
{
authorizedResultBlock();
}
}}];
} else if (authStatus == PHAuthorizationStatusAuthorized) {
isAvalible = YES;
if (authorizedResultBlock)
{
authorizedResultBlock();
}
} else
{
NSLog(@"没有相册权限");
}
return isAvalible;
}
#pragma mark - RPScreenRecordDelegate 暂时未用这个方法
-(void)screenRecorder:(RPScreenRecorder *)screenRecorder didStopRecordingWithPreviewViewController:(RPPreviewViewController *)previewViewController error:(NSError *)error{
NSLog(@"%@",error);
}
- (void)screenRecorderDidChangeAvailability:(RPScreenRecorder *)screenRecorder {
//如果变成了无法录屏,应该发送通知去停止录屏
NSLog(@"屏幕录制的能力改变了 %d %d",screenRecorder.isRecording,screenRecorder.isAvailable);
}
#pragma mark 判断取出的视频文件是不是在规定时间之内录制的
- (BOOL)videoTimeCheck:(NSDate *)videoDate {
if (!videoDate) {
//特殊情况备注
return YES;
}
//判断是否在5分钟之内录制
BOOL timeOK = [GMScreenRecorderManager getTimeChange:videoDate];
return timeOK;
}
+ (BOOL)getTimeChange:(NSDate *)time {
NSDate *currentTimeDate = [NSDate date]; // 现在时间
NSDate *getTimeDate = time;
// 现在时间 与 获取时间 之差
long dd = (long)[currentTimeDate timeIntervalSince1970] - [getTimeDate timeIntervalSince1970];
BOOL timeOK = NO;
// 1小时内。。
if (dd/3600<1){
if (dd/60 <= 2) {
timeOK = YES;
}else{
timeOK = NO;
}
}
return timeOK;
}
// 合成视频
- (void)mixAudioAndVidoWithInputURL2:(NSURL*)inputURL outPath:(NSString *)outPath {
//视频 声音 来源
NSURL *videoInputUrl = inputURL;
NSURL *audioInputUrl = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"ios音乐" ofType:@"mp3"]];
//合成之后的输出路径
NSString *outPutPath = outPath;
//混合后的视频输出路径
NSURL *outPutUrl = [NSURL fileURLWithPath:outPutPath];
if ([[NSFileManager defaultManager] fileExistsAtPath:outPutPath])
{
[[NSFileManager defaultManager] removeItemAtPath:outPutPath error:nil];
}
//时间起点
CMTime nextClistartTime = kCMTimeZero;
//创建可变的音视频组合
AVMutableComposition *comosition = [AVMutableComposition composition];
//视频采集
NSDictionary *options = @{AVURLAssetPreferPreciseDurationAndTimingKey:@YES};
AVURLAsset * videoAsset = [[AVURLAsset alloc] initWithURL:videoInputUrl options:options];
//视频时间范围
CMTimeRange videoTimeRange = CMTimeRangeMake(kCMTimeZero, videoAsset.duration);
// 视频通道 枚举 kCMPersistentTrackID_Invalid = 0
AVMutableCompositionTrack *videoTrack = [comosition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
//视频采集通道
AVAssetTrack * videoAssetTrack = [[videoAsset tracksWithMediaType:AVMediaTypeVideo] firstObject];
//把采集轨道数据加入到可变轨道中
[videoTrack insertTimeRange:videoTimeRange ofTrack:videoAssetTrack atTime:nextClistartTime error:nil];
//声音采集
AVURLAsset *audioAsset = [[AVURLAsset alloc] initWithURL:audioInputUrl options:options];
//因为视频较短 所以直接用了视频的长度 如果想要自动化需要自己写判断
CMTimeRange audioTimeRange = videoTimeRange;
//音频通道
AVMutableCompositionTrack * audioTrack = [comosition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
//音频采集通道
AVAssetTrack * audioAssetTrack = [[audioAsset tracksWithMediaType:AVMediaTypeAudio] firstObject];
//加入合成轨道中
[audioTrack insertTimeRange:audioTimeRange ofTrack:audioAssetTrack atTime:nextClistartTime error:nil];
//#warning test
// 3.1 - Create AVMutableVideoCompositionInstruction
AVMutableVideoCompositionInstruction *mainInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
mainInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, videoAsset.duration);
// 3.2 - Create an AVMutableVideoCompositionLayerInstruction for the video track and fix the orientation.
AVMutableVideoCompositionLayerInstruction *videolayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack];
UIImageOrientation videoAssetOrientation_ = UIImageOrientationUp;
BOOL isVideoAssetPortrait_ = NO;
CGAffineTransform videoTransform = videoAssetTrack.preferredTransform;
if (videoTransform.a == 0 && videoTransform.b == 1.0 && videoTransform.c == -1.0 && videoTransform.d == 0) {
videoAssetOrientation_ = UIImageOrientationRight;
isVideoAssetPortrait_ = YES;
}
if (videoTransform.a == 0 && videoTransform.b == -1.0 && videoTransform.c == 1.0 && videoTransform.d == 0) {
videoAssetOrientation_ = UIImageOrientationLeft;
isVideoAssetPortrait_ = YES;
}
if (videoTransform.a == 1.0 && videoTransform.b == 0 && videoTransform.c == 0 && videoTransform.d == 1.0) {
videoAssetOrientation_ = UIImageOrientationUp;
}
if (videoTransform.a == -1.0 && videoTransform.b == 0 && videoTransform.c == 0 && videoTransform.d == -1.0) {
videoAssetOrientation_ = UIImageOrientationDown;
}
[videolayerInstruction setTransform:videoAssetTrack.preferredTransform atTime:kCMTimeZero];
[videolayerInstruction setOpacity:0.0 atTime:videoAsset.duration];
// 3.3 - Add instructions
mainInstruction.layerInstructions = [NSArray arrayWithObjects:videolayerInstruction,nil];
AVMutableVideoComposition *mainCompositionInst = [AVMutableVideoComposition videoComposition];
CGSize naturalSize;
if(isVideoAssetPortrait_){
naturalSize = CGSizeMake(videoAssetTrack.naturalSize.height, videoAssetTrack.naturalSize.width);
} else {
naturalSize = videoAssetTrack.naturalSize;
}
float renderWidth, renderHeight;
renderWidth = naturalSize.width;
renderHeight = naturalSize.height;
mainCompositionInst.renderSize = CGSizeMake(renderWidth, renderHeight);
mainCompositionInst.instructions = [NSArray arrayWithObject:mainInstruction];
mainCompositionInst.frameDuration = CMTimeMake(1, 30);
//#warning test end 如果没有这段代码,合成后的视频会旋转90度
//创建输出
AVAssetExportSession * assetExport = [[AVAssetExportSession alloc] initWithAsset:comosition presetName:AVAssetExportPresetHighestQuality]; // 这里可以选择图片质量
assetExport.outputURL = outPutUrl; //输出路径
assetExport.shouldOptimizeForNetworkUse = YES; //是否优化 不太明白
assetExport.outputFileType = AVFileTypeMPEG4; //输出类型
assetExport.videoComposition = mainCompositionInst;
[assetExport exportAsynchronouslyWithCompletionHandler:^{ // 合成完毕
if (assetExport.status == AVAssetExportSessionStatusCompleted) {
dispatch_async(dispatch_get_main_queue(), ^{ // 保存到相册
if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum([outPutUrl path])) {
UISaveVideoAtPathToSavedPhotosAlbum([outPutUrl path], self, @selector(video:didFinishSavingWithError:contextInfo:), nil);
}
});
}else{ // 如果失败, 需要回调状态
dispatch_async(dispatch_get_main_queue(), ^{ // 保存到相册
[self showWarningWithText:@"压缩文件失败!"];
});
}
}];
}
// 保存视频到相册
- (void)video:(NSString*)videoPath didFinishSavingWithError:(NSError*)error contextInfo:(void*)contextInfo {
if (error) {
[self showWarningWithText:@"保存相册失败!"];
}else {
//取出这个视频
PHFetchOptions *options = [[PHFetchOptions alloc] init];
options.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:YES]]; //按创建日期获取
PHFetchResult *assetsFetchResults = [PHAsset fetchAssetsWithOptions:options];
PHAsset *phasset = [assetsFetchResults lastObject];
if (phasset) {
if (phasset.mediaType == PHAssetMediaTypeVideo) { // 是视频文件
__weak typeof(self) weakSelf = self;
PHImageManager *manager = [PHImageManager defaultManager];
[manager requestAVAssetForVideo:phasset options:nil resultHandler:^(AVAsset * _Nullable asset, AVAudioMix * _Nullable audioMix, NSDictionary * _Nullable info) {
AVURLAsset *urlAsset = (AVURLAsset *)asset;
// 返回成功的URL
dispatch_async(dispatch_get_main_queue(), ^{
//此处再次判断这个视频文件的时间防止误传,以5分钟为界限
if (![weakSelf videoTimeCheck:asset.creationDate.dateValue]) {
[weakSelf showWarningWithText:@"获取不到刚才的录屏文件,请重试!"];
return ;
}
// 代理成功回调
if (weakSelf.delegate && [weakSelf.delegate respondsToSelector:@selector(saveVideoSuscessWithAssetURl:)]) {
[weakSelf.delegate saveVideoSuscessWithAssetURl:urlAsset];
}
});
}];
} else {
[self showWarningWithText:@"未成功保存视频!"];
}
} else {
[self showWarningWithText:@"未成功保存视频!"];
}
}
// 删除沙盒缓存的视频文件
NSFileManager *fileManager = [NSFileManager defaultManager];
if([fileManager fileExistsAtPath:videoPath]){
[fileManager removeItemAtPath:videoPath error:nil];
NSLog(@"删除沙盒临时存储视频成功!");
}
}
// 结束录屏时, 失败提示!
- (void)showWarningWithText:(NSString *)text {
if (self.delegate && [self.delegate respondsToSelector:@selector(didStopRecordWithErrorString:)]) {
[self.delegate didStopRecordWithErrorString:text];
}
}
// 开启录屏时,失败提醒!
- (void)showStartRecordErrorWithText:(NSString *)text {
if (self.delegate && [self.delegate respondsToSelector:@selector(didStartRecordWithErrorString:)]) {
[self.delegate didStartRecordWithErrorString:text];
}
}
@end
//
// RPPreviewViewController+MovieURL.h
// LJRepayer
//
// Created by 刘鹿杰 on 2019/7/30.
// Copyright © 2019 刘鹿杰. All rights reserved.
//
#import <ReplayKit/ReplayKit.h>
@interface RPPreviewViewController (MovieURL)
/**
获取录屏的URL
*/
@property (nonatomic,strong) NSURL *movieURL;
@end
//
// RPPreviewViewController+MovieURL.m
// LJRepayer
//
// Created by 刘鹿杰 on 2019/7/30.
// Copyright © 2019 刘鹿杰. All rights reserved.
//
#import "RPPreviewViewController+MovieURL.h"
@implementation RPPreviewViewController (MovieURL)
@dynamic movieURL;
@end
//
// DaiVolume+AccessObject.h
// MusicPlayer
//
// Created by 啟倫 陳 on 2014/4/10.
// Copyright (c) 2014年 YuLiang. All rights reserved.
//
#import "DaiVolume.h"
@interface DaiVolume (AccessObject)
id volumeObject(id self);
@end
//
// DaiVolume+AccessObject.m
// MusicPlayer
//
// Created by 啟倫 陳 on 2014/4/10.
// Copyright (c) 2014年 YuLiang. All rights reserved.
//
#import "DaiVolume+AccessObject.h"
#import <MediaPlayer/MediaPlayer.h>
#import <objc/runtime.h>
@implementation DaiVolume (AccessObject)
static const char VOLUMEOBJECTPOINTER;
#pragma mark - class method
id volumeObject(id self) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
MPVolumeView *volumeView = [MPVolumeView new];
for (NSObject *obj in volumeView.subviews) {
if ([obj isKindOfClass:NSClassFromString(@"MPVolumeSlider")]) {
objc_setAssociatedObject(self, &VOLUMEOBJECTPOINTER, obj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
break;
}
}
});
return objc_getAssociatedObject(self, &VOLUMEOBJECTPOINTER);
}
@end
//
// DaiVolume+Invocation.h
// MusicPlayer
//
// Created by 啟倫 陳 on 2014/4/10.
// Copyright (c) 2014年 YuLiang. All rights reserved.
//
#import "DaiVolume.h"
@interface DaiVolume (Invocation)
NSInvocation* getCurrentVolumeInvocation(id self);
NSInvocation* setCurrentVolumeInvocation(id self, float volumeValue);
@end
//
// DaiVolume+Invocation.m
// MusicPlayer
//
// Created by 啟倫 陳 on 2014/4/10.
// Copyright (c) 2014年 YuLiang. All rights reserved.
//
#import "DaiVolume+Invocation.h"
#import "DaiVolume+AccessObject.h"
@implementation DaiVolume (Invocation)
#pragma mark - class method
NSInvocation* getCurrentVolumeInvocation(id self) {
static NSInvocation *invocation;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSMethodSignature *signature = [volumeObject(self) methodSignatureForSelector:NSSelectorFromString(@"value")];
invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget:volumeObject(self)];
[invocation setSelector:NSSelectorFromString(@"value")];
});
return invocation;
}
NSInvocation* setCurrentVolumeInvocation(id self, float volumeValue) {
static NSInvocation *invocation;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSMethodSignature *signature = [volumeObject(self) methodSignatureForSelector:NSSelectorFromString(@"setValue:")];
invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget:volumeObject(self)];
[invocation setSelector:NSSelectorFromString(@"setValue:")];
});
[invocation setArgument:&volumeValue atIndex:2];
return invocation;
}
@end
//
// DaiVolume.h
// MusicPlayer
//
// Created by 啟倫 陳 on 2014/4/10.
// Copyright (c) 2014年 YuLiang. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface DaiVolume : NSObject
+(float) volume;
+(void) setVolume : (float) volume;
@end
//
// DaiVolume.m
// MusicPlayer
//
// Created by 啟倫 陳 on 2014/4/10.
// Copyright (c) 2014年 YuLiang. All rights reserved.
//
#import "DaiVolume.h"
#import "DaiVolume+Invocation.h"
@interface DaiVolume ()
float getCurrentVolume(id self);
@end
@implementation DaiVolume
#pragma mark - class method
+(float) volume {
return getCurrentVolume(self);
}
+(void) setVolume : (float) volume {
[setCurrentVolumeInvocation(self, volume) invoke];
}
#pragma mark - private
float getCurrentVolume(id self) {
NSInvocation *invocation = getCurrentVolumeInvocation(self);
[invocation invoke];
float currentVolume;
[invocation getReturnValue:&currentVolume];
return currentVolume;
}
@end
import UIKit
import Alamofire
import SwiftyJSON
import ObjectMapper
private let AUTO_PLIST_OBJECT0_PATH = Bundle.main.path(forResource:"Province", ofType:"plist")
private let AUTO_PLIST_OBJECT1_PATH = Bundle.main.path(forResource: "city", ofType:"plist")
fileprivate func < <T : Comparable>(lhs: T?, rhs: T?) -> Bool {
switch (lhs, rhs) {
case let (l?, r?):
return l < r
case (nil, _?):
return true
default:
return false
}
}
fileprivate func > <T : Comparable>(lhs: T?, rhs: T?) -> Bool {
switch (lhs, rhs) {
case let (l?, r?):
return l > r
default:
return rhs < lhs
}
}
enum CompareResult {
case equal
case biger
case smaller
}
typealias Result = () -> Void
typealias ResultClouse = (Int) -> ()
public var checkArray:Array<String> = []
@objc class DataHelper: NSObject {
// 单例对象,demo项目中无用处,仅用于传值,建议替换,自己写传值就好了,原项目中有他用
static let SharedDataHelper = DataHelper()
var imageData = Data()
fileprivate override init() {
super.init()
}
}
//
// FaceStatePromter.swift
// Gengmei
//
// Created by 刘鹿杰 on 2019/11/5.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
import UIKit
enum FaceState {
case initial
case near
case right
case far
}
class FaceStatePromter {
// 加载资源
let initialSoundUrl = URL(fileURLWithPath: Bundle.main.path(forResource: "seeHor", ofType: "mp3")!)
let nearerSoundUrl = URL(fileURLWithPath: Bundle.main.path(forResource: "beFar", ofType: "mp3")!)
let fartherSoundUrl = URL(fileURLWithPath: Bundle.main.path(forResource: "beNear", ofType: "mp3")!)
let rightSoundUrl = URL(fileURLWithPath: Bundle.main.path(forResource: "closeEye_du", ofType: "mp3")!)
var duSound = URL(fileURLWithPath: Bundle.main.path(forResource: "duLoudly", ofType: "mp3")!)
var initialAudioPlayer: AVAudioPlayer!
init() {
do {
self.initialAudioPlayer = try AVAudioPlayer(contentsOf: self.initialSoundUrl)
initialAudioPlayer.prepareToPlay()
} catch {
print("sound file not found")
}
}
var cameraState = FaceState.initial
var state = FaceState.initial {
willSet {}
didSet {
switch state {
case .initial:
if UserDefaults.standard.bool(forKey: "noHumanVoice") == true { return }
if cameraState == .initial && initialAudioPlayer.isPlaying { return }
do {
try initialAudioPlayer = AVAudioPlayer(contentsOf: self.initialSoundUrl)
} catch {print("sound file not found")}
if !didSetHuman {
didSetHuman = true
if isEarPhone() {
if UserDefaults.standard.float(forKey: "humanVoice_earPhone") == 0.0 {
if AVAudioSession.sharedInstance().outputVolume > 0.5 || AVAudioSession.sharedInstance().outputVolume < 0.3 {
DaiVolume.setVolume(0.4)
}
} else {
DaiVolume.setVolume(UserDefaults.standard.float(forKey: "humanVoice_earPhone"))
}
} else if UserDefaults.standard.float(forKey: "humanVoice") == 0.0 {
if AVAudioSession.sharedInstance().outputVolume > 0.55 || AVAudioSession.sharedInstance().outputVolume < 0.75 {
switch UIDevice.current.modelName {
case "iPhone 7", "iPhone 7 Plus", "new One":
DaiVolume.setVolume(0.6)
default:
DaiVolume.setVolume(0.65)
}
}
} else {
// 进来时有值了 直接获取过来设置
DaiVolume.setVolume(UserDefaults.standard.float(forKey: "humanVoice"))
}
}
audioPlayVoice(facestate: .initial)
case .near:
if UserDefaults.standard.bool(forKey: "noHumanVoice") == true { return }
if cameraState == .near && initialAudioPlayer.isPlaying { return }
do {
try initialAudioPlayer = AVAudioPlayer(contentsOf: self.nearerSoundUrl)
} catch {}
audioPlayVoice(facestate: .near)
case .far:
if UserDefaults.standard.bool(forKey: "noHumanVoice") == true { return }
if cameraState == .far && initialAudioPlayer.isPlaying { return }
do {
try initialAudioPlayer = AVAudioPlayer(contentsOf: self.fartherSoundUrl)
} catch {}
audioPlayVoice(facestate: .far)
case .right:
if UserDefaults.standard.bool(forKey: "noHumanVoice") == true { return }
if cameraState == .right && initialAudioPlayer.isPlaying { return }
if !didSetNoHumam {
didSetNoHumam = true
if UserDefaults.standard.bool(forKey: "noHumanVoice") {
if isEarPhone() {
if UserDefaults.standard.float(forKey: "duVoice_earPhone") == 0.0 {
DaiVolume.setVolume(0.4)
} else {
DaiVolume.setVolume(UserDefaults.standard.float(forKey: "duVoice_earPhone"))
}
} else if UserDefaults.standard.float(forKey: "duVoice") == 0.0 {
switch UIDevice.current.modelName {
case "iPhone 7", "iPhone 7 Plus", "new One":
DaiVolume.setVolume(0.6)
default:
DaiVolume.setVolume(0.65)
}
} else {
DaiVolume.setVolume(UserDefaults.standard.float(forKey: "duVoice"))
}
}
}
do {
if UserDefaults.standard.bool(forKey: "noHumanVoice") == false {
try initialAudioPlayer = AVAudioPlayer(contentsOf: self.rightSoundUrl)
}
} catch {}
audioPlayVoice(facestate: .right)
}
}
}
// 播放声音
func audioPlayVoice(facestate: FaceState) {
if !initialAudioPlayer.isPlaying {
cameraState = facestate
initialAudioPlayer.prepareToPlay()
self.initialAudioPlayer.play()
}
}
// 停止音频播放
func stopPlayVoice() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
self.initialAudioPlayer.stop()
}
}
// 判断用户是否带耳机了
func isEarPhone() -> Bool {
let route = AVAudioSession.sharedInstance().currentRoute
for desc in route.outputs {
if desc.portType == AVAudioSessionPortHeadphones {
return true
}
}
return false
}
}
//
// GMFaceImageExtension.swift
// Gengmei
//
// Created by 刘鹿杰 on 2019/11/7.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
import Foundation
public extension UIImage {
func imageRotatedByDegrees(_ degrees: CGFloat, flip: Bool) -> UIImage {
let degreesToRadians: (CGFloat) -> CGFloat = {
return $0 / 180.0 * .pi
}
// calculate the size of the rotated view's containing box for our drawing space
let rotatedViewBox = UIView(frame: CGRect(origin: CGPoint.zero, size: size))
let t = CGAffineTransform(rotationAngle: degreesToRadians(degrees));
rotatedViewBox.transform = t
let rotatedSize = rotatedViewBox.frame.size
// Create the bitmap context
UIGraphicsBeginImageContext(rotatedSize)
let bitmap = UIGraphicsGetCurrentContext()
// Move the origin to the middle of the image so we will rotate and scale around the center.
bitmap?.translateBy(x: rotatedSize.width / 2.0, y: rotatedSize.height / 2.0);
bitmap?.rotate(by: degreesToRadians(degrees));
// Now, draw the rotated/scaled image into the context
var yFlip: CGFloat
if(flip){
yFlip = CGFloat(-1.0)
} else {
yFlip = CGFloat(1.0)
}
bitmap?.scaleBy(x: yFlip, y: -1.0)
bitmap?.draw(cgImage!, in: CGRect(x: -size.width / 2, y: -size.height / 2, width: size.width, height: size.height))
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!
}
// MARK: - 修改exif but ... i 存下来的
func addExif(_ container:ExifContainer) -> Data {
let imageData = UIImageJPEGRepresentation(self, 0.4)
let source = CGImageSourceCreateWithData(imageData! as CFData, nil)
let uti = CGImageSourceGetType(source!)
let dest_data = NSMutableData()
let destination = CGImageDestinationCreateWithData(dest_data, uti!, 1, nil)
if destination == nil {
print("can not create image destination")
}
CGImageDestinationAddImageFromSource(destination!, source!
, 0, container.exifData() as CFDictionary?)
var success = false
success = CGImageDestinationFinalize(destination!)
if !success {
print("can not create data from image destination")
}
return dest_data as Data
}
}
public extension UIDevice {
var modelName: String {
var systemInfo = utsname()
uname(&systemInfo)
let machineMirror = Mirror(reflecting: systemInfo.machine)
let identifier = machineMirror.children.reduce("") { identifier, element in
guard let value = element.value as? Int8 , value != 0 else { return identifier }
return identifier + String(UnicodeScalar(UInt8(value)))
}
switch identifier {
case "iPod5,1": return "iPod Touch 5"
case "iPod7,1": return "iPod Touch 6"
case "iPhone3,1", "iPhone3,2", "iPhone3,3": return "iPhone 4"
case "iPhone4,1": return "iPhone 4s"
case "iPhone5,1", "iPhone5,2": return "iPhone 5"
case "iPhone5,3", "iPhone5,4": return "iPhone 5c"
case "iPhone6,1", "iPhone6,2": return "iPhone 5s"
case "iPhone7,2": return "iPhone 6"
case "iPhone7,1": return "iPhone 6 Plus"
case "iPhone8,1": return "iPhone 6s"
case "iPhone8,2": return "iPhone 6s Plus"
case "iPhone8,4": return "iPhone SE"
case "iPhone9,1": return "iPhone 7"
case "iPhone9,3": return "iPhone 7"
case "iPhone9,2": return "iPhone 7 Plus"
case "iPhone9,4": return "iPhone 7 Plus"
case "iPhone10,1","iPhone10,4": return "iPhone 8"
case "iPhone10,2","iPhone10,5": return "iPhone 8 Plus"
case "iPhone10,3","iPhone10,6": return "iPhone X"
case "iPad2,1", "iPad2,2", "iPad2,3", "iPad2,4":return "iPad 2"
case "iPad3,1", "iPad3,2", "iPad3,3": return "iPad 3"
case "iPad3,4", "iPad3,5", "iPad3,6": return "iPad 4"
case "iPad4,1", "iPad4,2", "iPad4,3": return "iPad Air"
case "iPad5,3", "iPad5,4": return "iPad Air 2"
case "iPad2,5", "iPad2,6", "iPad2,7": return "iPad Mini"
case "iPad4,4", "iPad4,5", "iPad4,6": return "iPad Mini 2"
case "iPad4,7", "iPad4,8", "iPad4,9": return "iPad Mini 3"
case "iPad5,1", "iPad5,2": return "iPad Mini 4"
case "iPad6,7", "iPad6,8": return "iPad Pro"
case "AppleTV5,3": return "Apple TV"
case "i386", "x86_64": return "Simulator"
default: return "new One"
}
}
}
//
// Visage.swift
// FaceDetection
//
// Created by Julian Abentheuer on 21.12.14.
// Copyright (c) 2014 Aaron Abentheuer. All rights reserved.
//
import UIKit
import CoreImage
import AVFoundation
import ImageIO
import AssetsLibrary
import Photos
public let kScreenWidth = UIScreen.main.bounds.size.width
public let kScreenHeight = UIScreen.main.bounds.size.height
public var didSetHuman = false
public var didSetNoHumam = false
class Visage: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate {
var timeString: String? = nil
var exif:AnyObject? = nil
var volice:Float?
var imgWidth:CGFloat = 0
var beginToTakePicture = false
var didTakePicture = false
var isnoCheckFace = false
var getFace = 0
var visageCameraView : UIView = UIView(frame: CGRect(x: 0, y: 0, width: kScreenWidth, height: kScreenHeight))
var options = [String : AnyObject]()
fileprivate var rightFace = 0
fileprivate var farFace = 0
fileprivate var nearFace = 0
fileprivate var closeEyeTimes = 0
let faceStatePromter = FaceStatePromter()
fileprivate(set) var leftEyeClosed : Bool?
fileprivate(set) var rightEyeClosed : Bool?
fileprivate let visageTakenPictureNotification = Notification(name: Notification.Name(rawValue: "visageTakenPictureNotification"), object: nil)
fileprivate var captureDevice : AVCaptureDevice!
fileprivate var faceDetector : CIDetector?
fileprivate var videoDataOutput : AVCaptureVideoDataOutput?
fileprivate var videoDataOutputQueue : DispatchQueue?
fileprivate var captureSession : AVCaptureSession = AVCaptureSession()
fileprivate let notificationCenter : NotificationCenter = NotificationCenter.default
fileprivate var currentOrientation : Int?
fileprivate var stillImageOutput: AVCaptureStillImageOutput? = AVCaptureStillImageOutput()
var onlyFireNotificatonOnStatusChange : Bool = true
enum DetectorAccuracy {
case batterySaving
case higherPerformance
}
// 摄像头方向
enum CameraDevice {
case iSightCamera
case faceTimeCamera
}
// MARK: - 初始化
init(cameraPosition : CameraDevice, optimizeFor : DetectorAccuracy) {
super.init()
volice = AVAudioSession.sharedInstance().outputVolume
switch cameraPosition {
case .faceTimeCamera : self.captureSetup(AVCaptureDevice.Position.front)
case .iSightCamera : self.captureSetup(AVCaptureDevice.Position.back)
}
var faceDetectorOptions : [String : String]?
switch optimizeFor {
case .batterySaving : faceDetectorOptions = [CIDetectorAccuracy : CIDetectorAccuracyLow as String]
case .higherPerformance : faceDetectorOptions = [CIDetectorAccuracy : CIDetectorAccuracyHigh as String]
}
self.faceDetector = CIDetector(ofType: CIDetectorTypeFace, context: nil, options: faceDetectorOptions)
}
required convenience init!(coder aDecoder: NSCoder!) {
fatalError("init(coder:) has not been implemented")
}
fileprivate func captureSetup (_ position : AVCaptureDevice.Position) {
for testedDevice in AVCaptureDevice.devices(for: AVMediaType.video){
if ((testedDevice as AnyObject).position == position) {
captureDevice = testedDevice
}
}
if (captureDevice == nil) {
captureDevice = AVCaptureDevice.default(for: AVMediaType.video)
}
if (captureDevice.hasFlash && captureDevice.hasTorch){
do {
try captureDevice.lockForConfiguration()
if kScreenWidth < 420 {
if captureDevice.isFlashModeSupported(AVCaptureDevice.FlashMode.on) {
captureDevice.flashMode = AVCaptureDevice.FlashMode.on
}
}
captureDevice.unlockForConfiguration()
} catch let error as NSError {
print("Could not lock device for configuration\(error)")
}
}
var deviceInput : AVCaptureDeviceInput
do {
deviceInput = try AVCaptureDeviceInput(device: captureDevice)
captureSession.sessionPreset = AVCaptureSession.Preset.photo
if (captureSession.canAddInput(deviceInput)) {
captureSession.addInput(deviceInput)
}
self.videoDataOutput = AVCaptureVideoDataOutput()
self.videoDataOutput!.videoSettings = [kCVPixelBufferPixelFormatTypeKey as AnyHashable as! String : Int(kCVPixelFormatType_32BGRA)]
self.videoDataOutput!.alwaysDiscardsLateVideoFrames = true
self.videoDataOutputQueue = DispatchQueue(label: "VideoDataOutputQueue", attributes: [])
self.videoDataOutput!.setSampleBufferDelegate(self, queue: self.videoDataOutputQueue)
if (captureSession.canAddOutput(self.videoDataOutput!)) {
captureSession.addOutput(self.videoDataOutput!)
}
visageCameraView.frame = CGRect(x: 0, y: 0, width: kScreenWidth, height: kScreenHeight)
let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer.frame = CGRect(x: 0, y: 0, width: kScreenWidth, height: kScreenHeight)
previewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
visageCameraView.layer.addSublayer(previewLayer)
stillImageOutput!.outputSettings = [AVVideoCodecKey: AVVideoCodecJPEG]
if captureSession.canAddOutput(stillImageOutput!) {
captureSession.addOutput(stillImageOutput!)
}
} catch _ {}
}
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
if isnoCheckFace == true {
return
}
if (!beginToTakePicture){
guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {return}
let sourceImage = CIImage(cvPixelBuffer: imageBuffer)
_ = imageFormSampleBuffer(sampleBuffer)
options = [CIDetectorSmile : false as AnyObject, CIDetectorEyeBlink: true as AnyObject, CIDetectorImageOrientation : 6 as AnyObject]
// MARK: - 识别图像人脸
guard let face = self.faceDetector else {return}
let features = face.features(in: sourceImage, options: options)
if (features.count != 0) { // 如果等于0 就是未检测到人脸
let feature = features[0] as! CIFaceFeature
let faceOriginX = feature.bounds.origin.x
let faceOriginY = feature.bounds.origin.y
let faceWidth = feature.bounds.width
let faceHeight = feature.bounds.height
let focusPoint = CGPoint(x: (faceOriginX + faceWidth)/2, y: (faceOriginY + faceHeight)/2)
if (feature.leftEyeClosed || feature.rightEyeClosed) {
closeEyeTimes += 1
}else {
closeEyeTimes = 0
}
if faceWidth > imgWidth * 0.6 && faceWidth < imgWidth * 0.76 {
getFace += 1
farFace = 0
nearFace = 0
rightFace += 1
}else if faceWidth < imgWidth * 0.6{
getFace = 0
farFace += 1
nearFace = 0
rightFace = 0
}else if faceWidth > imgWidth * 0.76 {
getFace = 0
nearFace += 1
farFace = 0
rightFace = 0
}
switch UIDevice.current.modelName {
case "iPhone 5","iPhone 5c","iPhone 5s","iPhone 6","iPhone 6 Plus":
if farFace >= 2{
faceStatePromter.state = FaceState.far
}
if nearFace >= 2{
faceStatePromter.state = FaceState.near
}
if rightFace >= 2 {
faceStatePromter.state = FaceState.right
}
default:
if farFace >= 3{
faceStatePromter.state = FaceState.far
}
if nearFace >= 3{
faceStatePromter.state = FaceState.near
}
if rightFace >= 3 {
faceStatePromter.state = FaceState.right
}
}
if (feature.leftEyeClosed && feature.rightEyeClosed && faceStatePromter.state == FaceState.right) {
closeEyeTimes += 1
}else {
closeEyeTimes = 0
}
switch UIDevice.current.modelName {
case "iPhone 5","iPhone 5c","iPhone 5s","iPhone 6","iPhone 6 Plus":
if getFace > 1 {
if (closeEyeTimes >= 2 && !beginToTakePicture && !didTakePicture) {
distanceRightAndCloseEyes(focusPoint: focusPoint)
}
}
default:
if getFace > 2 {
if (closeEyeTimes >= 3 && !beginToTakePicture && !didTakePicture) {
distanceRightAndCloseEyes(focusPoint: focusPoint)
}
}
}
}
}
}
// 距离合适且已经闭眼 符合拍照模式
func distanceRightAndCloseEyes(focusPoint:CGPoint){
faceStatePromter.initialAudioPlayer.stop()
self.captureImage(focusPoint)
self.beginToTakePicture = true
captureDevice.unlockForConfiguration()
setHumanVoice()
if AVAudioSession.sharedInstance().outputVolume != volice {
DaiVolume.setVolume(volice!)
}
}
//MARK - 设置人声
func setHumanVoice() {
didSetHuman = false
didSetNoHumam = false
if !UserDefaults.standard.bool(forKey: "noHumanVoice") {
// 人声 戴耳机 拍照后设置音量
if isEarPhone() {
if AVAudioSession.sharedInstance().outputVolume > 0.6 {
UserDefaults.standard.set(0.6, forKey: "humanVoice_earPhone")
}else if AVAudioSession.sharedInstance().outputVolume < 0.1 {
UserDefaults.standard.set(0.1, forKey: "humanVoice_earPhone")
}else {
UserDefaults.standard.set(AVAudioSession.sharedInstance().outputVolume, forKey: "humanVoice_earPhone")
}
}else {
// 人声 无耳机 拍照后设置音量
if AVAudioSession.sharedInstance().outputVolume > 0.9 {
UserDefaults.standard.set(0.9, forKey: "humanVoice")
}else if AVAudioSession.sharedInstance().outputVolume < 0.3 {
UserDefaults.standard.set(0.3, forKey: "humanVoice")
}else {
UserDefaults.standard.set(AVAudioSession.sharedInstance().outputVolume, forKey: "humanVoice")
}
}
}else {
if isEarPhone() {
if AVAudioSession.sharedInstance().outputVolume > 0.6 {
UserDefaults.standard.set(0.6, forKey: "doVoice_earPhone")
}else if AVAudioSession.sharedInstance().outputVolume < 0.1 {
UserDefaults.standard.set(0.1, forKey: "doVoice_earPhone")
}else {
UserDefaults.standard.set(AVAudioSession.sharedInstance().outputVolume, forKey: "doVoice_earPhone")
}
}else {
if AVAudioSession.sharedInstance().outputVolume < 0.3 {
UserDefaults.standard.set(0.3, forKey: "duVoice")
}else if AVAudioSession.sharedInstance().outputVolume > 0.9 {
UserDefaults.standard.set(0.9, forKey: "duVoice")
}else {
UserDefaults.standard.set(AVAudioSession.sharedInstance().outputVolume, forKey: "duVoice")
}
}
}
}
// MARK: - 初始化
func beginFaceDetection() {
self.captureSession.startRunning()
}
func endFaceDetection() {
self.captureSession.stopRunning()
}
func isEarPhone() -> Bool {
let route = AVAudioSession.sharedInstance().currentRoute
for desc in route.outputs {
if desc.portType == AVAudioSessionPortHeadphones {
return true
}
}
return false
}
func convertCGImageToCIImage(_ inputImage: CGImage) -> CIImage! {
let ciImage = CIImage(cgImage: inputImage)
return ciImage
}
func imageFormSampleBuffer(_ sampleBuffer:CMSampleBuffer) {
let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)
CVPixelBufferLockBaseAddress(imageBuffer!, CVPixelBufferLockFlags(rawValue: CVOptionFlags(0)))
let height = CVPixelBufferGetHeight(imageBuffer!)
imgWidth = CGFloat(height)
}
func captureImage(_ focusPoint: CGPoint){
do {
try self.captureDevice!.lockForConfiguration()
} catch let error as NSError {
NSLog("Could not lock device for configuration: %@", error)
}
self.captureDevice!.unlockForConfiguration()
if let videoConnection = stillImageOutput!.connection(with: AVMediaType.video) {
videoConnection.videoOrientation = AVCaptureVideoOrientation.portrait
stillImageOutput?.captureStillImageAsynchronously(from: videoConnection, completionHandler: {(sampleBuffer, error) in
if (error == nil) {
if (sampleBuffer != nil) {
self.endFaceDetection()
if (CMGetAttachment(sampleBuffer!, kCGImagePropertyExifDictionary, nil) != nil) {
//尝试捕捉
guard let buffer = sampleBuffer else {
return
}
self.exif = CMGetAttachment(buffer, kCGImagePropertyExifDictionary, nil) as! NSDictionary
}
guard let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(sampleBuffer!) else {
return
}
self.saveToFileSystem(imageData)
self.didTakePicture = true
do {
try self.captureDevice.lockForConfiguration()
} catch _ {
}
self.captureDevice.unlockForConfiguration()
self.notificationCenter.post(self.visageTakenPictureNotification)
// 难道是这?
self.notificationCenter.removeObserver(self.visageTakenPictureNotification)
}
}
})
}
}
deinit {
captureDevice = nil
}
func saveToFileSystem(_ imageData: Data) {
let dataProvider = CGDataProvider(data: imageData as CFData)
var cgImage = CGImage(jpegDataProviderSource: dataProvider!, decode: nil, shouldInterpolate: true, intent: CGColorRenderingIntent.defaultIntent)!
if cgImage.width > 3264 {
cgImage = resizeCGImageBySize(cgImage: cgImage, width: 3264, height: 2448)
}
let takenImage = UIImage(cgImage: cgImage, scale: 1.0, orientation: UIImage.Orientation.up)
let rotatedImage = takenImage.imageRotatedByDegrees(90, flip: false)
let date = Date()
// 存exif信息
let container = ExifContainer()
container.addCreationDate(Date.init(timeIntervalSinceNow: 0))
container.addExifDictionary(self.exif as? [AnyHashable : Any])
let imagedataWithExif = rotatedImage.addExif(container)
let formatter = DateFormatter()
formatter.dateFormat = "yyyyMMdd_HHmmss"
timeString = formatter.string(from: date)
if imagedataWithExif.count == 0 {
DataHelper.SharedDataHelper.imageData = UIImageJPEGRepresentation(rotatedImage, 0.4)!
}else {
DataHelper.SharedDataHelper.imageData = imagedataWithExif
}
}
// MARK: resize CGimage
func resizeCGImageBySize(cgImage: CGImage, width: Int, height: Int) -> CGImage {
let bitsPerComponent = cgImage.bitsPerComponent
let bytesPerRow = cgImage.bytesPerRow
let colorSpace = cgImage.colorSpace
let bitmapInfo = cgImage.bitmapInfo
let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace!, bitmapInfo: bitmapInfo.rawValue)
context!.interpolationQuality = CGInterpolationQuality.high
context?.draw(cgImage, in: CGRect(origin: CGPoint.zero, size: CGSize(width: CGFloat(width), height: CGFloat(height))))
let cgImage = context?.makeImage()
return cgImage!
}
}
//
// ExifContainer.h
// Pods
//
// Created by Nikita Tuk on 02/02/15.
//
//
#import <Foundation/Foundation.h>
@class CLLocation;
@interface ExifContainer : NSObject
- (void)addLocation:(CLLocation *)currentLocation;
- (void)addUserComment:(NSString*)comment;
- (void)addCreationDate:(NSDate *)date;
- (void)addDescription:(NSString*)description;
- (void)addExifDictionary:(NSDictionary *)objects;
- (NSDictionary *)exifData;
@end
//
// ExifContainer.m
// Pods
//
// Created by Nikita Tuk on 02/02/15.
//
//
#import <ImageIO/ImageIO.h>
#import <CoreLocation/CoreLocation.h>
#import "ExifContainer.h"
@interface ExifContainer ()
@property (nonatomic, strong) NSMutableDictionary *imageMetadata;
@property (nonatomic, strong, readonly) NSMutableDictionary *exifDictionary;
@property (nonatomic, strong, readonly) NSMutableDictionary *tiffDictionary;
@property (nonatomic, strong, readonly) NSMutableDictionary *gpsDictionary;
@end
@implementation ExifContainer
- (instancetype)init {
self = [super init];
if (self) {
_imageMetadata = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void)addExifDictionary:(NSDictionary *)objects{
[self.exifDictionary setDictionary:objects];
}
- (void)addLocation:(CLLocation *)currentLocation {
CLLocationDegrees latitude = currentLocation.coordinate.latitude;
CLLocationDegrees longitude = currentLocation.coordinate.longitude;
NSString *latitudeRef = nil;
NSString *longitudeRef = nil;
if (latitude < 0.0) {
latitude *= -1;
latitudeRef = @"S";
} else {
latitudeRef = @"N";
}
if (longitude < 0.0) {
longitude *= -1;
longitudeRef = @"W";
} else {
longitudeRef = @"E";
}
self.gpsDictionary[(NSString*)kCGImagePropertyGPSTimeStamp] = [self getUTCFormattedDate:currentLocation.timestamp];
self.gpsDictionary[(NSString*)kCGImagePropertyGPSLatitudeRef] = latitudeRef;
self.gpsDictionary[(NSString*)kCGImagePropertyGPSLatitude] = [NSNumber numberWithFloat:latitude];
self.gpsDictionary[(NSString*)kCGImagePropertyGPSLongitudeRef] = longitudeRef;
self.gpsDictionary[(NSString*)kCGImagePropertyGPSLongitude] = [NSNumber numberWithFloat:longitude];
self.gpsDictionary[(NSString*)kCGImagePropertyGPSDOP] = [NSNumber numberWithFloat:currentLocation.horizontalAccuracy];
self.gpsDictionary[(NSString*)kCGImagePropertyGPSAltitude] = [NSNumber numberWithFloat:currentLocation.altitude];
self.exifDictionary[(NSString*)kCGImagePropertyExifBodySerialNumber] = [NSNumber numberWithInt:5];
self.exifDictionary[(NSString*)kCGImagePropertyExifWhiteBalance] = [NSNumber numberWithInt:10];
}
- (void)addUserComment:(NSString*)comment {
[self.exifDictionary setObject:comment forKey:(NSString*)kCGImagePropertyExifUserComment];
}
- (void)addCreationDate:(NSDate *)date {
NSString *dateString = [self getUTCFormattedDate:date];
[self.exifDictionary setObject:dateString forKey:(NSString*)kCGImagePropertyExifDateTimeOriginal];
}
- (void)addDescription:(NSString*)description {
[self.tiffDictionary setObject:description forKey:(NSString *)kCGImagePropertyTIFFImageDescription];
}
- (NSDictionary *)exifData {
return self.imageMetadata;
}
#pragma mark - Getters
- (NSMutableDictionary *)exifDictionary {
return [self dictionaryForKey:(NSString*)kCGImagePropertyExifDictionary];
}
- (NSMutableDictionary *)tiffDictionary {
return [self dictionaryForKey:(NSString*)kCGImagePropertyTIFFDictionary];
}
- (NSMutableDictionary *)gpsDictionary {
return [self dictionaryForKey:(NSString*)kCGImagePropertyGPSDictionary];
}
- (NSMutableDictionary *)dictionaryForKey:(NSString *)key {
NSMutableDictionary *dict = self.imageMetadata[key];
if (!dict) {
dict = [[NSMutableDictionary alloc] init];
self.imageMetadata[key] = dict;
}
return dict;
}
#pragma mark - Helpers
- (NSString *)getUTCFormattedDate:(NSDate *)localDate {
static NSDateFormatter *dateFormatter = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy:MM:dd HH:mm:ss"];
});
return [dateFormatter stringFromDate:localDate];
}
@end
//
// UIImage+Exif.h
// Pods
//
// Created by Nikita Tuk on 12/02/15.
//
//
#import <UIKit/UIKit.h>
@class ExifContainer;
@interface UIImage (Exif)
- (NSData *)addExif:(ExifContainer *)container;
@end
//
// UIImage+Exif.m
// Pods
//
// Created by Nikita Tuk on 12/02/15.
//
//
#import <ImageIO/ImageIO.h>
#import "UIImage+Exif.h"
#import "ExifContainer.h"
@implementation UIImage (Exif)
- (NSData *)addExif:(ExifContainer *)container {
NSData *imageData = UIImageJPEGRepresentation(self, 1.0f);
// create an imagesourceref
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef) imageData, NULL);
// this is the type of image (e.g., public.jpeg)
CFStringRef UTI = CGImageSourceGetType(source);
NSDictionary * metadata = (NSDictionary *) CFBridgingRelease(CGImageSourceCopyPropertiesAtIndex(source, 0, NULL));
NSMutableDictionary *metadataAsMutable = [metadata mutableCopy];
NSMutableDictionary *EXIFDictionary = [[metadataAsMutable objectForKey:(NSString *)kCGImagePropertyExifDictionary]mutableCopy];
if(!EXIFDictionary)
{
EXIFDictionary = [NSMutableDictionary dictionary];
}
[EXIFDictionary setValue:@"xml_user_comment" forKey:(NSString *)kCGImagePropertyExifUserComment];
[metadataAsMutable setObject:EXIFDictionary forKey:(NSString *)kCGImagePropertyExifDictionary];
//add our modified EXIF data back into the image’s metadata
// create a new data object and write the new image into it
NSMutableData *dest_data = [NSMutableData data];
CGImageDestinationRef destination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)dest_data, UTI, 1, NULL);
if (!destination) {
NSLog(@"Error: Could not create image destination");
}
// add the image contained in the image source to the destination, overidding the old metadata with our modified metadata
CGImageDestinationAddImageFromSource(destination, source, 0, (__bridge CFDictionaryRef) metadataAsMutable);
BOOL success = NO;
success = CGImageDestinationFinalize(destination);
if (!success) {
NSLog(@"Error: Could not create data from image destination");
}
if (destination != nil) {
CFRelease(destination);
}
CFRelease(source);
return dest_data;
}
@end
//
// FaceShotBottomView.swift
// Gengmei
//
// Created by Locus on 2019/8/13.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
import UIKit
class FaceShotBottomCell: GMCollectionViewCell {
let titleLabel = UILabel()
let dotView = UIView()
var selectedCell: Bool = false {
didSet {
if selectedCell {
titleLabel.font = UIFont.gmBoldFont(16)
dotView.isHidden = false
} else {
titleLabel.font = UIFont.gmFont(14)
dotView.isHidden = true;
}
}
}
override func setup() {
titleLabel.font = UIFont.gmFont(13)
titleLabel.textColor = UIColor.white
addSubview(titleLabel)
titleLabel.snp.makeConstraints { (make) in
make.bottom.equalTo(-12);
make.centerX.equalToSuperview()
}
dotView.backgroundColor = UIColor.white
dotView.isHidden = true;
dotView.layer.cornerRadius = 2
dotView.layer.masksToBounds = true;
addSubview(dotView)
dotView.snp.makeConstraints { (make) in
make.top.equalTo(titleLabel.snp.bottom).offset(4)
make.centerX.equalToSuperview()
make.width.height.equalTo(4)
}
}
}
@objc protocol FaceShotBottomViewDelegate {
func faceShotBottomCellDidSelected(type : GMScanAnimationType)
}
class FaceShotBottomView: UIView, UICollectionViewDataSource, UICollectionViewDelegate {
weak var delegate: FaceShotBottomViewDelegate?
var animationType: GMScanAnimationType = .faceApp
var collectionView: UICollectionView?
var selectedCell: FaceShotBottomCell? {
willSet {
if (newValue != nil) && (newValue!.selectedCell == false) {
guard let index = collectionView?.indexPath(for: newValue!)?.row else {
return
}
animationType = GMScanAnimationType(rawValue: index) ?? .faceApp
selectedCell?.selectedCell = false
newValue!.selectedCell = true
UIView.animate(withDuration: 0.2) {
self.mj_x = (UIScreen.main.bounds.size.width - 180) / 2.0 - CGFloat((index - 1) * 60);
}
// 添加代理方法
if delegate != nil {
print(self.animationType)
delegate?.faceShotBottomCellDidSelected(type: self.animationType)
}
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupUI() {
let layout = UICollectionViewFlowLayout.init()
layout.itemSize = CGSize(width: 60, height: 30)
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
layout.scrollDirection = .horizontal
collectionView = UICollectionView.init(frame: CGRect(x:0, y:0, width:self.width, height:self.height), collectionViewLayout: layout)
collectionView?.backgroundColor = UIColor.clear
collectionView?.delegate = self
collectionView?.dataSource = self
addSubview(collectionView!)
collectionView?.register(cell: FaceShotBottomCell.classForCoder())
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell:FaceShotBottomCell = collectionView.dequeue(cell: FaceShotBottomCell.classForCoder(), for: indexPath) as! FaceShotBottomCell
if indexPath.row == 0 {
cell.titleLabel.text = "人脸拟合"
cell.selectedCell = true
selectedCell = cell
} else if indexPath.row == 1 {
cell.titleLabel.text = "AI测脸"
cell.selectedCell = false
} else if indexPath.row == 2 {
cell.titleLabel.text = "AI测肤"
cell.selectedCell = false
}
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedCell = collectionView.cellForItem(at: indexPath) as? FaceShotBottomCell
}
}
//
// GMDrawDottedView.h
// Gengmei
//
// Created by 刘鹿杰 on 2019/6/27.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger,GMLineType){
drawViewTypeVerticalDotted = 0, // 竖线
drawViewTypeHorizontalDotted = 1 // 横线
};
typedef NS_ENUM(NSInteger,GMWordPlace){
wordPlaceCenter = 0, // 文字显示在中间
wordPlaceTop = 1, // 文字显示在顶部
wordPlaceRight = 2, // 文字显示在右侧
wordPlaceBottom = 3, // 文字显示在底侧
wordPlaceLeft = 4, // 文字显示在左侧
};
@interface GMDrawDottedView : GMView
// 只画虚线
- (instancetype)initWithLineType:(GMLineType)lineType;
// 画带文字的线
-(instancetype)initWithLineType:(GMLineType)lineType addlabel:(GMWordPlace)wordPlace addDect:(NSString *)dect;
@end
NS_ASSUME_NONNULL_END
//
// GMDrawDottedView.m
// Gengmei
//
// Created by 刘鹿杰 on 2019/6/27.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMDrawDottedView.h"
#define HArrowSize CGSizeMake(4.5, 3.5) // 水平线时箭头宽高
#define VArrowSize CGSizeMake(3.5, 4.5) // 竖直线时箭头宽高
#define HLineStrat 6.5 // 画虚线的起始点位置
#define arrowWidth 3.5
@interface GMDrawDottedView()
@property (nonatomic, assign) GMLineType lineType; // 控制横竖线
@property (nonatomic, assign) GMWordPlace wordPlace; // 位置
@property (nonatomic, copy) NSString *dect; // 展示文字
@property (nonatomic, strong) UIImageView *leftImageView;
@property (nonatomic, strong) UIImageView *rigthImageView;
@property (nonatomic, strong) UIImageView *upImageView;
@property (nonatomic, strong) UIImageView *bottomImageView;
@property (nonatomic, strong) GMLabel *textLabel; // 展示文字label
@property (nonatomic, assign) CGSize labelSize; // 文字的尺寸
@property (nonatomic, strong)NSMutableArray *pointArray;
@end
@implementation GMDrawDottedView
- (instancetype)initWithLineType:(GMLineType)lineType {
if (self = [super init]) {
self.lineType = lineType;
[self setUpUI];
}
return self;
}
- (instancetype)initWithLineType:(GMLineType)lineType addlabel:(GMWordPlace)wordPlace addDect:(NSString *)dect {
if (self = [super init]) {
self.lineType = lineType;
self.wordPlace = wordPlace;
self.dect = dect;
[self calculateSize];
[self setUpUI];
}
return self;
}
- (void)calculateSize {
self.labelSize = [self.dect sizeWithAttributes:@{NSFontAttributeName:[UIFont gmFont:10]}];
}
- (void)setUpUI {
if (self.lineType == drawViewTypeHorizontalDotted) {
self.leftImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"facev3_arrowleft"]];
[self addSubview:_leftImageView];
self.rigthImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"facev3_arrowrigth"]];
[self addSubview:_rigthImageView];
}else{
self.leftImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"facev3_arrowtop"]];
[self addSubview:_leftImageView];
self.rigthImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"facev3_arrowdown"]];
[self addSubview:_rigthImageView];
}
self.textLabel = [GMLabel labelWithTextColor:UIColor.whiteColor fontSize:9];
self.textLabel.shadowColor = [UIColor colorWithHexString:@"000000" alpha:0.3];
self.textLabel.shadowOffset = CGSizeMake(0, 1);
self.textLabel.layer.shadowRadius = 0.5;
_textLabel.textAlignment = NSTextAlignmentCenter;
_textLabel.numberOfLines = 2;
_textLabel.text = self.dect;
[self addSubview:_textLabel];
// 添加约束
[self addDectLabelConstraints];
}
// 添加文字约束
- (void)addDectLabelConstraints {
if (self.lineType == drawViewTypeHorizontalDotted) { // 如果为横线
[self addBaseHorizontalConstraints]; // 添加基本布局
if (self.wordPlace == wordPlaceCenter) {
[self addHorizontalCenterDottedConstraints]; // 文字在中间
}else if (self.wordPlace == wordPlaceTop) {
[self addHorizontalTopDottedConstraints]; // 文字在上面
}else {
[self addHorizontalBottomDottedConstraints]; // 文字在下面
}
}
if (self.lineType == drawViewTypeVerticalDotted) { // 如果为竖线
[self addBaseVerticalConstraints]; // 添加基本布局
if (self.wordPlace == wordPlaceCenter) {
[self addvertlcalCenterDottedConstraints]; // 文字在中间
}else if (self.wordPlace == wordPlaceLeft) {
[self addvertlcalLeftDottedConstraints]; // 文字在左侧
}else { // 文字在下面
[self addvertlcalRightDottedConstraints]; // 文字在右侧
}
}
}
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
CGFloat hLineWidth = self.width - HLineStrat * 2;
CGFloat hLinehight = self.height - HLineStrat * 2;
CGContextRef context = UIGraphicsGetCurrentContext();
[[UIColor whiteColor] set];
CGContextSetLineWidth(context, 0.5);
CGFloat length[] = {2,2};
CGContextSetLineDash(context, 0, length, 2);
if (self.lineType == drawViewTypeHorizontalDotted) { // 横线
if (self.wordPlace == wordPlaceCenter) {
CGContextMoveToPoint(context, HLineStrat, self.height / 2);
CGContextAddLineToPoint(context, (hLineWidth - self.labelSize.width)/ 2 + HLineStrat, self.height / 2);
CGContextMoveToPoint(context, (hLineWidth + self.labelSize.width) / 2 + HLineStrat, self.height / 2);
CGContextAddLineToPoint(context, hLineWidth + HLineStrat, self.height / 2);
}else if (self.wordPlace == wordPlaceTop) {
CGContextMoveToPoint(context, HLineStrat, self.height - arrowWidth / 2);
CGContextAddLineToPoint(context, hLineWidth + HLineStrat,self.height - arrowWidth / 2);
}else{
CGContextMoveToPoint(context, HLineStrat, arrowWidth / 2);
CGContextAddLineToPoint(context, hLineWidth + HLineStrat, arrowWidth / 2);
}
}else{ // 竖线
if (self.wordPlace == wordPlaceCenter) {
CGFloat centerX = self.width / 2;
CGContextMoveToPoint(context, centerX, HLineStrat);
CGContextAddLineToPoint(context, centerX, (hLinehight - self.labelSize.height)/ 2 + HLineStrat);
CGContextMoveToPoint(context, centerX, (hLinehight + self.labelSize.height) / 2 + HLineStrat);
CGContextAddLineToPoint(context, centerX, hLinehight + HLineStrat);
}else if (self.wordPlace == wordPlaceRight) {
CGContextMoveToPoint(context, arrowWidth / 2, HLineStrat);
CGContextAddLineToPoint(context, arrowWidth / 2,hLinehight + HLineStrat);
}else{
CGContextMoveToPoint(context, self.width - arrowWidth / 2, HLineStrat);
CGContextAddLineToPoint(context, self.width - arrowWidth / 2,hLinehight + HLineStrat);
}
}
CGContextStrokePath(context);
}
#pragma mark 为水平线时的约束
// 为水平线时,leftImageView、rigthImageView 基本布局
- (void)addBaseHorizontalConstraints {
[_textLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo(_labelSize.width);
make.height.mas_equalTo(_labelSize.height);
make.centerX.mas_equalTo(self.centerX);
}];
[self.leftImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo(HArrowSize.width);
make.height.mas_equalTo(HArrowSize.height);
make.left.mas_equalTo(0);
}];
[self.rigthImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo(HArrowSize.width);
make.height.mas_equalTo(HArrowSize.height);
make.right.mas_equalTo(0);
}];
}
// 水平线 - 文字在中心位置
- (void)addHorizontalCenterDottedConstraints {
[_textLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.mas_equalTo(self.center);
}];
[self.leftImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.mas_equalTo(self.centerY);
}];
[self.rigthImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.mas_equalTo(self.centerY);
}];
}
// 水平线 - 文字在上放位置
- (void)addHorizontalTopDottedConstraints {
[_textLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.top);
}];
[self.leftImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.bottom.mas_equalTo(self.bottom);
}];
[self.rigthImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.bottom.mas_equalTo(self.bottom);
}];
}
// 水平线 - 文字在下方位置
- (void)addHorizontalBottomDottedConstraints {
[self.leftImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.top);
}];
[self.rigthImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.top);
}];
[_textLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.bottom.mas_equalTo(self.bottom);
}];
}
#pragma mark 为竖线时的约束
// 为竖线时,leftImageView、rigthImageView 基本布局
- (void)addBaseVerticalConstraints {
[_textLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo(_labelSize.width);
make.height.mas_equalTo(_labelSize.height);
make.centerY.mas_equalTo(self.centerY);
}];
[self.leftImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo(VArrowSize.width);
make.height.mas_equalTo(VArrowSize.height);
make.top.mas_equalTo(self.top);
}];
[self.rigthImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo(VArrowSize.width);
make.height.mas_equalTo(VArrowSize.height);
make.bottom.mas_equalTo(self.bottom);
}];
}
- (void)addvertlcalCenterDottedConstraints {
[_textLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.mas_equalTo(self.center);
}];
[self.leftImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.mas_equalTo(self.centerX);
}];
[self.rigthImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.mas_equalTo(self.centerX);
}];
}
- (void)addvertlcalLeftDottedConstraints {
[_textLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.left);
}];
[self.leftImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.mas_equalTo(self.right);
}];
[self.rigthImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.mas_equalTo(self.right);
}];
}
- (void)addvertlcalRightDottedConstraints {
[self.leftImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.left);
}];
[self.rigthImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.left);
}];
[_textLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.mas_equalTo(self.right);
}];
}
@end
//
// GMFaceBottomView.swift
// Gengmei
//
// Created by Locus on 2019/9/18.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
import Foundation
import GMBaseSwift
protocol GMFaceBottomViewDelegate: NSObjectProtocol {
func faceBottomCellDidSelected(_ type: GMScanAnimationType)
}
class GMFaceBottomView: GMView, GMFaceSegmentViewDelegate {
var selectedType: GMScanAnimationType = .scanFace {
willSet {
segmentView.selectedType = newValue
albumButton.isHidden = newValue == .scanSkin
courseButton.isHidden = (newValue == .scanFace) || (newValue == .geneMemory)
shotButton.isEnabled = newValue == .scanFace
}
}
weak var delegate: GMFaceBottomViewDelegate?
// // 底部按钮封装
// var segmentView: GMFaceShotBottomView!
// 底部按钮封装
var segmentView: GMFaceSegmentView!
// 拍照按钮
var shotButton: GMButton!
// 选择相册按钮
var albumButton: GMFaceButton!
// 教程按钮
var courseButton: GMFaceButton!
// 上次报告
var lastReportButton: GMFaceButton!
override func setup() {
let bottomView = GMView()
bottomView.backgroundColor = .white
self.addSubview(bottomView)
bottomView.snp.makeConstraints { (make) in
make.edges.equalToSuperview()
}
// 拍照按钮
shotButton = GMButton(type: .custom)
shotButton.setImage(UIImage(named: "face_shot_normal"), for: .normal)
shotButton.setImage(UIImage(named: "face_shot_disabled"), for: .disabled)
self.addSubview(shotButton)
shotButton.snp.makeConstraints { (make) in
make.bottom.equalTo(-62 - UIView.safeAreaInsetsBottom)
make.centerX.equalToSuperview()
make.size.equalTo(CGSize(width: 70, height: 70))
}
segmentView = GMFaceSegmentView()
segmentView.delegate = self
self.addSubview(segmentView)
segmentView.snp.makeConstraints { (make) in
make.size.equalTo(CGSize(width: 325, height: 40))
make.centerX.equalToSuperview()
make.top.equalTo(shotButton.snp_bottom).offset(10)
}
// segmentView = GMFaceShotBottomView()
// segmentView.delegate = self
// self.addSubview(segmentView)
// segmentView.snp.makeConstraints { (make) in
// make.size.equalTo(CGSize(width: 325, height: 40))
// make.centerX.equalToSuperview()
// make.top.equalTo(shotButton.snp_bottom).offset(10)
// }
albumButton = GMFaceButton()
albumButton.faceLabel.text = "相册"
albumButton.faceImageView.layer.borderColor = UIColor.separatorLine.cgColor
albumButton.faceImageView.layer.borderWidth = 1
albumButton.faceImageView.image = UIImage(named: "NewFacePhotoNone")
self.addSubview(albumButton)
albumButton.snp.makeConstraints { (make) in
make.centerY.equalTo(shotButton)
make.centerX.equalTo(shotButton).offset(-Constant.screenWidth * 0.25 - 12)
make.size.equalTo(CGSize(width: 35, height: 52.5))
}
courseButton = GMFaceButton()
courseButton.isHidden = true
courseButton.faceImageView.image = UIImage.init(named: "face_skin_touchbtn")
courseButton.faceLabel.text = "教程"
courseButton.addTarget(self, action: #selector(GMFaceBottomView.goTestSkinTeachVC), for: .touchUpInside)
self.addSubview(courseButton)
courseButton.snp.makeConstraints { (make) in
make.centerY.equalTo(shotButton)
make.centerX.equalTo(shotButton).offset(-Constant.screenWidth * 0.25 - 12)
make.size.equalTo(CGSize(width: 35, height: 52.5))
}
lastReportButton = GMFaceButton()
lastReportButton.faceImageView.image = UIImage.init(named: "face_report_icon")
lastReportButton.faceLabel.text = "上次报告"
lastReportButton.addTarget(self, action: #selector(GMFaceBottomView.lastReportButtonBtnClick), for: .touchUpInside)
self.addSubview(lastReportButton)
lastReportButton.snp.makeConstraints { (make) in
make.centerY.equalTo(shotButton)
make.centerX.equalTo(shotButton).offset(Constant.screenWidth * 0.25 + 12)
make.size.equalTo(CGSize(width: 44, height: 52.5))
}
}
// 上次报告 页面跳转
@objc func lastReportButtonBtnClick() {
if selectedType == .scanFace { // 扫脸
Phobos.track("on_click_button", attributes: ["page_name": "face_scan","button_name": "last_report","tab_name": "AI测脸"]);
let vc = GMFaceInfoShareViewController()
AppDelegate.navigation.pushViewController(vc, animated: true)
} else if selectedType == .scanSkin { // 测肤
Phobos.track("on_click_button", attributes: ["page_name": "face_scan","button_name": "last_report", "tab_name": "AI测肤"])
let vc = GMTestSkinAnimationWebView()
AppDelegate.navigation.pushViewController(vc, animated: true)
} else if selectedType == .geneMemory { // 面孔起源
Phobos.track("on_click_button", attributes: ["page_name": "face_scan","button_name": "last_report", "tab_name": "AI面孔"])
let vc = GMGeneMemoryResultViewController()
AppDelegate.navigation.pushViewController(vc, animated: true)
}
}
// 去测肤页面
@objc func goTestSkinTeachVC() {
let testSkinVc = GMTestSkinTeachVC()
AppDelegate.navigation.pushViewController(testSkinVc, animated: true)
}
func faceShotBottomCellDidSelected(_ type: GMScanAnimationType) {
if selectedType != type {
selectedType = type
if delegate != nil {
delegate?.faceBottomCellDidSelected(type)
}
}
}
func segmentViewItemDidSelected(_ type: GMScanAnimationType) {
if selectedType != type {
selectedType = type
if delegate != nil {
delegate?.faceBottomCellDidSelected(type)
}
}
}
}
//
// GMFaceButton.swift
// Gengmei
//
// Created by Locus on 2019/9/18.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
import Foundation
class GMFaceButton: GMButton {
var faceImageView : GMImageView!
var faceLabel : GMLabel!
override func setup() {
faceImageView = GMImageView.init()
faceImageView.layer.cornerRadius = 4
faceImageView.layer.masksToBounds = true
faceImageView.contentMode = .scaleAspectFill
self.addSubview(faceImageView)
faceImageView.snp.makeConstraints { (make) in
make.size.equalTo(CGSize.init(width: 35, height: 35))
make.centerX.top.equalToSuperview()
}
faceLabel = GMLabel.init(textColor: UIColor.auxiliaryTextDark, fontSize: 11)
faceLabel.textAlignment = .center
self.addSubview(faceLabel)
faceLabel.snp.makeConstraints { (make) in
make.centerX.bottom.equalToSuperview()
make.size.equalTo(CGSize.init(width: 44, height: 11))
}
}
}
//
// GMFacePhotosPopView.h
// Gengmei
//
// Created by 刘鹿杰 on 2019/12/26.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@class GMAIFacePopViewObject;
@interface GMFacePhotosPopView : GMView
@property (nonatomic, strong) GMAIFacePopViewObject *popModel;
@end
NS_ASSUME_NONNULL_END
//
// GMFacePhotosPopView.m
// Gengmei
//
// Created by 刘鹿杰 on 2019/12/26.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMFacePhotosPopView.h"
#import "GMAIFacePopViewObject.h"
#import "NSMutableAttributedString+Attachment.h"
#define popViewHeight (100 + OCNavigationBar.statusBarHeight)
@interface GMFacePhotosPopView ()
@property (nonatomic, strong) UIImageView *iconImage;
@property (nonatomic, strong) GMLabel *titleLabel;
@property (nonatomic, strong) GMLabel *contentLabel;
@end
@implementation GMFacePhotosPopView
- (void)setup {
[super setup];
self.backgroundColor = [UIColor whiteColor];
// 添加底部关闭按钮
self.iconImage = [[UIImageView alloc] init];
_iconImage.userInteractionEnabled = YES;
[self addSubview:_iconImage];
[_iconImage mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(OCNavigationBar.statusBarHeight + 15);
make.left.mas_equalTo(15);
make.width.height.mas_equalTo(70);
}];
// 添加title
self.titleLabel = [[GMLabel alloc] init];
_titleLabel.textColor = UIColor.bodyText;
_titleLabel.font = [UIFont gmBoldFont:16];
_titleLabel.textAlignment = NSTextAlignmentLeft;
_titleLabel.numberOfLines = 2;
[self addSubview:_titleLabel];
[_titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(_iconImage.mas_top);
make.left.mas_equalTo(_iconImage.mas_right).offset(10);
make.right.mas_equalTo(-50);
}];
// 添加内容
self.contentLabel = [GMLabel labelWithTextColor:UIColor.auxiliaryTextDark fontSize:12];
_contentLabel.textAlignment = NSTextAlignmentLeft;
[self addSubview:_contentLabel];
[_contentLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(_titleLabel.mas_left);
make.right.mas_equalTo(_titleLabel.mas_right);
make.bottom.mas_equalTo(_iconImage.mas_bottom);
make.height.mas_equalTo(12);
}];
// 添加关闭按钮
GMButton *closebtn = [[GMButton alloc] init];
[self addSubview:closebtn];
[closebtn addTarget:self action:@selector(closeBtnClick) forControlEvents:UIControlEventTouchUpInside];
[closebtn setImage:[UIImage imageNamed:@"face_skin_popview_hidden"] forState:UIControlStateNormal];
[closebtn setImage:[UIImage imageNamed:@"face_skin_popview_hidden"] forState:UIControlStateHighlighted];
[closebtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.mas_equalTo(-15);
make.top.mas_equalTo(_iconImage.mas_top);
make.width.height.mas_equalTo(20);
}];
// 添加点击 和清扫手势效果
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(pushViewComtroller)];
[self addGestureRecognizer:tap];
}
-(void)setPopModel:(GMAIFacePopViewObject *)popModel {
_popModel = popModel;
[self.iconImage sd_setImageWithURL:[NSURL URLWithString:popModel.imageUrl]];
NSMutableAttributedString *attribute = [[NSMutableAttributedString alloc] initWithString:@" "];
[attribute appendAttributedString:[[NSAttributedString alloc] initWithString:SafeString(popModel.title)]];
// 文字前面插入图片
[attribute addAIPopViewAttachment]; // 添加图片
//设置行间距
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
[paragraphStyle setLineSpacing:3];
[paragraphStyle setLineBreakMode:NSLineBreakByWordWrapping];
[paragraphStyle setAlignment:NSTextAlignmentLeft];
[attribute addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0, [popModel.title length])];
self.titleLabel.attributedText = attribute;
self.contentLabel.text = popModel.content;
}
- (void)pushViewComtroller { // 跳转控制器
[self removeFromSuperview];
[Phobos track:@"on_click_button" attributes:@{@"page_name" : @"face_detect_result", @"button_name" :@"learn_more"}];
[[GMRouter sharedInstance] pushScheme:_popModel.gmUrl];
// [AppDelegate.visibleController pushScheme:_popModel.gmUrl];
}
-(void)closeBtnClick {
[UIView animateWithDuration:0.25 animations:^{
self.mj_y = (-popViewHeight);
} completion:^(BOOL finished) {
[Phobos track:@"on_click_button" attributes:@{@"page_name" : @"face_detect_result", @"button_name" :@"close"}];
[self removeFromSuperview];
}];
}
@end
//
// GMFaceSegmentView.h
// Gengmei
//
// Created by 朱璇 on 2019/12/17.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
typedef NS_ENUM(NSInteger, GMScanAnimationType) {
GMScanAnimationTypeScanFace = 0, // AI侧脸
GMScanAnimationTypeScanSkin = 1, // AI测肤
GMScanAnimationTypeGeneMemory = 2, // AI面孔
};
@protocol GMFaceSegmentViewDelegate <NSObject>
-(void)segmentViewItemDidSelected:(GMScanAnimationType)type;
@end
@interface GMfaceTypeCell : GMCollectionViewCell
- (void)setCellSelected:(BOOL)selected;
@property (nonatomic, assign) BOOL isSelected;
@end
@interface GMFaceSegmentView : GMView
@property (nonatomic, weak) id<GMFaceSegmentViewDelegate> delegate;
// 选中的type
@property (nonatomic, assign) GMScanAnimationType selectedType;
- (void)setSelectedCellWithType:(GMScanAnimationType)type;
@end
//
// GMFaceSegmentView.m
// Gengmei
//
// Created by 朱璇 on 2019/12/17.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMFaceSegmentView.h"
#import "GMFaceSegmentViewLayout.h"
#import <AudioToolbox/AudioToolbox.h>
#define itemWidth 65
#define itemHeight 40
@interface GMAIDotNotificationView : GMView
@property (nonatomic, strong) GMView *dotView;
@end
@implementation GMAIDotNotificationView
static CGFloat kRedDotViewWidth = 3;
- (void)setup {
[super setup];
[self addSubview:self.dotView];
}
- (void)showWithSuperView:(UIView *)superView top:(CGFloat)top left:(CGFloat)left {
[superView addSubview:self];
self.frame = CGRectMake(left, top, kRedDotViewWidth, kRedDotViewWidth);
}
- (void)hide {
[self removeFromSuperview];
}
- (GMView *)dotView {
if (!_dotView) {
_dotView = [[GMView alloc] init];
_dotView.backgroundColor = UIColor.auxiliaryTextGreen;
_dotView.layer.cornerRadius = kRedDotViewWidth / 2.0;
_dotView.layer.masksToBounds = YES;
[self addSubview:_dotView];
[_dotView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self);
}];
}
return _dotView;
}
@end
@interface GMfaceTypeCell()
@property (nonatomic, strong) GMLabel *titleLabel;
@end
@implementation GMfaceTypeCell
- (void)setup {
[super setup];
self.layer.cornerRadius = 7;
_titleLabel = [[GMLabel alloc] init];
_titleLabel.textColor = UIColor.headlineText;
_titleLabel.font = [UIFont gmFont:12];
_titleLabel.textAlignment = NSTextAlignmentCenter;
[self addSubview:_titleLabel];
[_titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(self);
}];
}
- (void)setIsSelected:(BOOL)isSelected {
if (isSelected) {
_titleLabel.textColor = UIColor.auxiliaryTextGreen;
_titleLabel.font = [UIFont gmBoldFont:12];
} else {
_titleLabel.textColor = UIColor.headlineText;
_titleLabel.font = [UIFont gmFont:12];
}
}
- (void)setCellSelected:(BOOL)selected {
}
@end
@interface GMFaceSegmentView()<UICollectionViewDataSource, UICollectionViewDelegate>
@property (nonatomic, strong) GMCollectionView *segmentCollectionView;
@property (nonatomic, strong) NSMutableArray *dataSource;
@property (nonatomic, strong) GMAIDotNotificationView *dotView;
@end
static NSString *kShowedSkinDotView = @"kShowedSkinDotView";
@implementation GMFaceSegmentView
- (void)setup {
[super setup];
_dataSource = [NSMutableArray arrayWithObjects:@"面孔起源", @"颜值分析", @"肤质分析", nil];
GMFaceSegmentViewLayout *layout = [[GMFaceSegmentViewLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
layout.sectionInset = UIEdgeInsetsMake(0, 0, 0, 0);
// 设置最小行间距
layout.minimumLineSpacing = 0;
layout.minimumInteritemSpacing = 0;
_segmentCollectionView = [[GMCollectionView alloc] initWithFrame:CGRectMake(0, 0, itemWidth * 5, itemHeight) collectionViewLayout:layout];
_segmentCollectionView.backgroundColor = UIColor.whiteColor;
_segmentCollectionView.delegate = self;
_segmentCollectionView.dataSource = self;
_segmentCollectionView.showsVerticalScrollIndicator = NO;
_segmentCollectionView.showsHorizontalScrollIndicator = NO;
[_segmentCollectionView registerClass:[GMfaceTypeCell class] forCellWithReuseIdentifier:NSStringFromClass([GMfaceTypeCell class])];
[self addSubview:_segmentCollectionView];
[_segmentCollectionView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.bottom.right.left.mas_equalTo(0);
}];
[self setSelectedCellWithType:GMScanAnimationTypeScanSkin];
[self.segmentCollectionView setContentOffset:CGPointMake(itemWidth * 2, 0)];
}
- (void)setSelectedCellWithType:(GMScanAnimationType)type {
NSArray *cells = [_segmentCollectionView visibleCells];
for (GMfaceTypeCell *cell in cells) {
NSIndexPath *indexPath = [_segmentCollectionView indexPathForCell:cell];
if (indexPath.section == 1) {
cell.isSelected = indexPath.row == [self getIndexWithType:type];
}
}
}
- (void)setSelectedType:(GMScanAnimationType)selectedType {
_selectedType = selectedType;
[self setSelectedCellWithType:selectedType];
[self.segmentCollectionView setContentOffset:CGPointMake([self getIndexWithType:selectedType] * itemWidth, 0) animated:YES];
if (selectedType == GMScanAnimationTypeScanSkin) {
[_dotView hide];
}
}
- (int)getIndexWithType:(GMScanAnimationType)type {
if (type == GMScanAnimationTypeScanFace) {
return 1;
} else if (type == GMScanAnimationTypeScanSkin) {
return 2;
} else if (type == GMScanAnimationTypeGeneMemory){
return 0;
}
return 2;
}
- (GMScanAnimationType)getTypeWithIndex:(NSInteger)index {
if (index == 1) {
return GMScanAnimationTypeScanFace;
} else if (index == 2) {
return GMScanAnimationTypeScanSkin;
} else if (index == 0){
return GMScanAnimationTypeGeneMemory;
}
return GMScanAnimationTypeScanSkin;
}
// 改变按钮选中状态
-(void)btnChange:(NSInteger)tag {
AudioServicesPlaySystemSound(1519);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (self.delegate && [self.delegate respondsToSelector:@selector(segmentViewItemDidSelected:)]) {
[self.delegate segmentViewItemDidSelected:[self getTypeWithIndex:tag]];
NSString *tabName = @"";
NSInteger position = tag;
if (tag == 0) {
tabName = @"AI面孔";
position = 3;
} else if (tag == 1) {
tabName = @"AI测脸";
} else if (tag == 2) {
tabName = @"AI测肤";
}
[Phobos track:@"on_click_tab" attributes:@{@"page_name": @"face_scan", @"position": @(position - 1),@"tab_name": tabName}];
}
});
}
#pragma mark - UICollectionViewDelegate
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 3;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
if (section == 0) {
return 2;
} else if (section == 1) {
return _dataSource.count;
}
return 2;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
GMfaceTypeCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass([GMfaceTypeCell class]) forIndexPath:indexPath];
NSString *lastShowDate = [[NSUserDefaults standardUserDefaults] objectForKey:kShowedSkinDotView];
NSString *currentDate = [[NSDate date] dateFormatToString];
if (indexPath.section == 1) {
if (indexPath.row == 2 && (!lastShowDate || ![currentDate isEqualToString:lastShowDate])) {
_dotView = [[GMAIDotNotificationView alloc] init];
[_dotView showWithSuperView:cell top:6 left:itemWidth - 7];
[[NSUserDefaults standardUserDefaults] setObject:currentDate forKey:kShowedSkinDotView];
}
cell.isSelected = indexPath.row == 2;
cell.titleLabel.text = _dataSource[indexPath.row];
}
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
GMfaceTypeCell *cell = (GMfaceTypeCell *)[collectionView cellForItemAtIndexPath:indexPath];
if (cell.isSelected) {
return;
}
[self btnChange:indexPath.row];
}
//动态设置每个Item的尺寸大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
return CGSizeMake(itemWidth, itemHeight);
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
if (!decelerate) {
[self btnChange: scrollView.contentOffset.x / itemWidth];
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
BOOL scrollToScrollStop = !scrollView.tracking && !scrollView.dragging && !scrollView.decelerating;
if (scrollToScrollStop) {
[self btnChange: scrollView.contentOffset.x / itemWidth];
}
}
@end
//
// GMFaceSegmentViewLayout.h
// Gengmei
//
// Created by 朱璇 on 2019/12/17.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface GMFaceSegmentViewLayout : UICollectionViewFlowLayout
@end
NS_ASSUME_NONNULL_END
//
// GMFaceSegmentViewLayout.m
// Gengmei
//
// Created by 朱璇 on 2019/12/17.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMFaceSegmentViewLayout.h"
@implementation GMFaceSegmentViewLayout
// 什么时候调用:用户手指一松开就会调用
// 作用:确定最终偏移量
// 定位:距离中心点越近,这个cell最终展示到中心点
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity{
// 拖动比较快 最终偏移量 不等于 手指离开时偏移量
CGFloat collectionW = self.collectionView.bounds.size.width;
// 最终偏移量
CGPoint targetP = [super targetContentOffsetForProposedContentOffset:proposedContentOffset withScrollingVelocity:velocity];
// 0.获取最终显示的区域
CGRect targetRect = CGRectMake(targetP.x, 0, collectionW, MAXFLOAT);
// 1.获取最终显示的cell
NSArray *attrs = [super layoutAttributesForElementsInRect:targetRect];
// 获取最小间距
CGFloat minDelta = MAXFLOAT;
for (UICollectionViewLayoutAttributes *attr in attrs) {
// 获取距离中心点距离:注意:应该用最终的x
CGFloat delta = (attr.center.x - targetP.x) - self.collectionView.bounds.size.width * 0.5;
if (fabs(delta) < fabs(minDelta)) {
minDelta = delta;
}
}
// 移动间距
targetP.x += minDelta;
if (targetP.x < 0) {
targetP.x = 0;
}
return targetP;
}
// Invalidate:刷新
// 在滚动的时候是否允许刷新布局
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
return YES;
}
@end
//
// GMFaceShotBottomView.h
// Gengmei
//
// Created by 刘鹿杰 on 2019/8/20.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, GMScanAnimationType) {
GMScanAnimationTypeScanFace = 0, // AI侧脸
GMScanAnimationTypeScanSkin = 1, // AI测肤
GMScanAnimationTypeGeneMemory = 2, // AI面孔
};
@protocol GMFaceShotBottomViewDelegate <NSObject>
-(void)faceShotBottomCellDidSelected:(GMScanAnimationType)type;
@end
@interface GMFaceShotBottomView : GMView
@property (nonatomic, weak)id<GMFaceShotBottomViewDelegate> delegate;
// 选中的type
@property (nonatomic, assign)GMScanAnimationType selectedType;
- (void)selectedBtnWithTag:(NSInteger)tag;
@end
NS_ASSUME_NONNULL_END
//
// GMFaceShotBottomView.m
// Gengmei
//
// Created by 刘鹿杰 on 2019/8/20.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMFaceShotBottomView.h"
#import <AudioToolbox/AudioToolbox.h>
#define BtnWidth 65
@interface ALDotNotificationView : GMView
@property (nonatomic, strong) GMView *dotView;
@end
@implementation ALDotNotificationView
static CGFloat kRedDotViewWidth = 3;
- (void)setup {
[super setup];
[self addSubview:self.dotView];
}
- (void)showWithSuperView:(UIView *)superView top:(CGFloat)top left:(CGFloat)left {
[superView addSubview:self];
self.frame = CGRectMake(left, top, kRedDotViewWidth, kRedDotViewWidth);
}
- (void)hide {
[self removeFromSuperview];
}
- (GMView *)dotView {
if (!_dotView) {
_dotView = [[GMView alloc] init];
_dotView.backgroundColor = UIColor.auxiliaryTextGreen;
_dotView.layer.cornerRadius = kRedDotViewWidth / 2.0;
_dotView.layer.masksToBounds = YES;
[self addSubview:_dotView];
[_dotView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self);
}];
}
return _dotView;
}
@end
@interface GMFaceShotBottomView()<UIScrollViewDelegate>
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) GMView *container;
@property (nonatomic, strong) ALDotNotificationView *dotView;
@end
@implementation GMFaceShotBottomView
static NSString *kShowedSkinDotView = @"kShowedSkinDotView";
-(void)setup {
[super setup];
_scrollView = [[UIScrollView alloc] init];
_scrollView.userInteractionEnabled = YES;
_scrollView.pagingEnabled = YES;
_scrollView.showsHorizontalScrollIndicator = NO;
_scrollView.contentSize = CGSizeMake(BtnWidth * 5 + 1, 0);
_scrollView.contentInset = UIEdgeInsetsMake(0, BtnWidth, 0, BtnWidth);
// _scrollView.contentOffset = CGPointMake(-BtnWidth, 0);
_scrollView.delegate = self;
[self addSubview:_scrollView];
[_scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.insets(UIEdgeInsetsZero);
}];
NSArray *array = @[@"颜值分析", @"肤质分析", @"面孔起源"];
NSString *lastShowDate = [[NSUserDefaults standardUserDefaults] objectForKey:kShowedSkinDotView];
NSString *currentDate = [[NSDate date] dateFormatToString];
for (int i = 0; i < array.count ; i++ ) {
GMButton *btn = [[GMButton alloc] init];
[btn.titleLabel setFont:[UIFont gmBoldFont:12]];
[btn setTitle:array[i] forState:UIControlStateNormal];
[btn setTitleColor:UIColor.headlineText forState:UIControlStateNormal];
[btn setTitleColor:UIColor.auxiliaryTextGreen forState:UIControlStateSelected];
if (i == 0) {
btn.selected = YES;
[btn.titleLabel setFont:[UIFont gmBoldFont:12]];
} else {
[btn.titleLabel setFont:[UIFont gmFont:12]];
}
btn.tag = i;
[btn addTarget:self action:@selector(btnCLick:) forControlEvents:UIControlEventTouchUpInside];
[_scrollView addSubview:btn];
if (i == 1 && (!lastShowDate || ![currentDate isEqualToString:lastShowDate])) {
_dotView = [[ALDotNotificationView alloc] init];
[_dotView showWithSuperView:btn top:6 left:BtnWidth - 7];
[[NSUserDefaults standardUserDefaults] setObject:currentDate forKey:kShowedSkinDotView];
}
[btn mas_updateConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(BtnWidth * i);
make.size.mas_equalTo(CGSizeMake(BtnWidth, 30));
make.centerY.mas_equalTo(self.mas_centerY);
}];
}
}
-(void)setSelectedType:(GMScanAnimationType)selectedType{
_selectedType = selectedType;
self.scrollView.contentOffset = CGPointMake(BtnWidth * (selectedType - 2), 0);
[self selectedBtnWithTag:selectedType];
if (selectedType == GMScanAnimationTypeScanSkin) {
[_dotView hide];
}
}
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
debugLog(@"=========targetContentOffset%.2f", targetContentOffset->x)
#warning 待修改
if (targetContentOffset->x < 0) { // 选中第一个按钮
[self btnChage:0];
} else if (targetContentOffset->x >= 0 && targetContentOffset->x < BtnWidth) { // 选中第二个按钮
[self btnChage:1];
} else {
[self btnChage:2];
}
}
-(void)btnCLick:(GMButton *)sender{
if (sender.selected) {
return;
}
if (sender.tag == 0) {
[self.scrollView setContentOffset:CGPointMake(-BtnWidth * 2, 0) animated:YES];
} else if(sender.tag == 1){
[_dotView hide];
[self.scrollView setContentOffset:CGPointMake(-BtnWidth, 0) animated:YES];
} else if(sender.tag == 2){
[self.scrollView setContentOffset:CGPointMake(0, 0) animated:YES];
}
[self btnChage:sender.tag];
}
- (void)selectedBtnWithTag:(NSInteger)tag{
for (GMButton *btn in _scrollView.subviews) {
if ([btn isKindOfClass:[GMButton class]]) {
btn.selected = btn.tag == tag;
[btn.titleLabel setFont:btn.tag == tag ? [UIFont gmBoldFont:12] : [UIFont gmFont:12]];
}
}
}
// 改变按钮选中状态
-(void)btnChage:(NSInteger)tag {
AudioServicesPlaySystemSound(1519);
[self selectedBtnWithTag:tag];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (self.delegate && [self.delegate respondsToSelector:@selector(faceShotBottomCellDidSelected:)]) {
[self.delegate faceShotBottomCellDidSelected:tag];
NSString *tabName = @"";
if (tag == 0) {
tabName = @"AI测脸";
} else if (tag == 1) {
tabName = @"AI测肤";
} else if (tag == 2) {
tabName = @"AI面孔";
}
[Phobos track:@"on_click_tab" attributes:@{@"page_name": @"face_scan", @"position": @(tag + 1),@"tab_name": tabName}];
}
});
}
@end
//
// GMFaceShotPopView.h
// Gengmei
//
// Created by 刘鹿杰 on 2019/7/9.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMPopupBgView.h"
NS_ASSUME_NONNULL_BEGIN
@interface GMFaceShotPopView : GMPopupBgView
@end
NS_ASSUME_NONNULL_END
//
// GMFaceShotPopView.m
// Gengmei
//
// Created by 刘鹿杰 on 2019/7/9.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMFaceShotPopView.h"
@interface GMFaceShotPopView()
@end
@implementation GMFaceShotPopView
- (void)setup {
[super setup];
self.animationType = GMPopupAnimationTypeFade;
// 设置 container
self.container.backgroundColor = [UIColor whiteColor];
self.container.frame = CGRectMake(53, (MAINSCREEN_HEIGHT - 370) / 2 - 50, MAINSCREEN_WIDTH - 106, 370);
self.container.layer.cornerRadius = 10;
self.container.clipsToBounds = YES;
// 添加三个View
GMView *view1 = [self creatViewWithimageName:@"facev4_popView1" title:@"平视摄像头" dect:@"脸部在拍照区域内"];
view1.frame = CGRectMake(0, 5, self.container.width, 120);
GMView *view2 = [self creatViewWithimageName:@"facev4_popView2" title:@"摘下眼镜" dect:@"眉毛眼睛清晰露出"];
view2.frame = CGRectMake(0, 125, self.container.width, 120);
GMView *view3 = [self creatViewWithimageName:@"facev4_popView3" title:@"点击拍照" dect:@"生成脸型风格报告"];
view3.frame = CGRectMake(0, 245, self.container.width, 120);
[self.container addSubview:view1];
[self.container addSubview:view2];
[self.container addSubview:view3];
// 添加底部关闭按钮
UIImageView *closeIcon = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"sign_rule_close"]];
closeIcon.frame = CGRectMake((MAINSCREEN_WIDTH - 35) / 2, CGRectGetMaxY(self.container.frame) + 20, 35, 35);
closeIcon.userInteractionEnabled = YES;
[self addSubview:closeIcon];
// 添加点击效果
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(hideView)];
[self addGestureRecognizer:tap];
}
- (GMView *)creatViewWithimageName:(NSString *)imageName title:(NSString *)title dect:(NSString *)dect {
GMView *bgView = [[GMView alloc] init];
bgView.backgroundColor = [UIColor whiteColor];
bgView.frame = CGRectMake(0, 0, MAINSCREEN_WIDTH - 106, 120);
// imageView
GMImageView *imageView = [[GMImageView alloc] initWithImage:[UIImage imageNamed:imageName]];
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.frame = CGRectMake(28, 20, 70, 80);
imageView.userInteractionEnabled = YES;
imageView.backgroundColor = UIColor.clearColor;
[bgView addSubview:imageView];
// title
GMLabel *titleLabel = [[GMLabel alloc] init];
titleLabel.textColor = UIColor.headlineText;
titleLabel.font = [UIFont gmBoldFont:15];
titleLabel.frame = CGRectMake(123, 41, bgView.width - 143, 15);
titleLabel.text = title;
[bgView addSubview:titleLabel];
// dect
GMLabel *dectLabel = [GMLabel labelWithTextColor:UIColor.auxiliaryTextDark fontSize:13];
dectLabel.text = dect;
dectLabel.frame = CGRectMake(123, 64, bgView.width - 143, 15);
[bgView addSubview:dectLabel];
return bgView;
}
- (void)hideView {
[self hide];
}
@end
//
// GMGeneMemoryGenderSelectView.h
// Gengmei
//
// Created by 朱璇 on 2019/12/5.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMPopupBgView.h"
@protocol GMGeneMemoryGenderSelectViewDelegate <NSObject>
// 选择性别
- (void)didClickGender:(NSInteger)gender;
@end
@interface GMGeneMemoryGenderSelectView : GMPopupBgView
@property(nonatomic, assign)id<GMGeneMemoryGenderSelectViewDelegate> selectDelegate;
@end
//
// GMGeneMemoryGenderSelectView.m
// Gengmei
//
// Created by 朱璇 on 2019/12/5.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMGeneMemoryGenderSelectView.h"
@implementation GMGeneMemoryGenderSelectView
-(void)setup{
[super setup];
self.animationType = GMPopupAnimationTypeFade;
self.container.backgroundColor = UIColor.clearColor;
[self.container mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self);
}];
UILabel *titleLabel = [[UILabel alloc] init];
titleLabel.text = @"开始扫脸前";
titleLabel.textColor = UIColor.whiteColor;
titleLabel.textAlignment = NSTextAlignmentCenter;
titleLabel.font = [UIFont gmFont:14];
[self addSubview:titleLabel];
[titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(174.0 / 667 * MAINSCREEN_HEIGHT);
make.centerX.mas_equalTo(0);
make.height.mas_equalTo(14);
}];
UILabel *contentLabel = [[UILabel alloc] init];
contentLabel.text = @"请先选择您的性别";
contentLabel.textColor = UIColor.whiteColor;
contentLabel.textAlignment = NSTextAlignmentCenter;
contentLabel.font = [UIFont gmFont:14];
[self addSubview:contentLabel];
[contentLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(titleLabel.mas_bottom).offset(20);
make.centerX.mas_equalTo(0);
make.height.mas_equalTo(14);
}];
UIButton *femaleBtn = [UIButton buttonWithType:UIButtonTypeCustom];
femaleBtn.backgroundColor = [UIColor colorWithHex:0 alpha:.1];
femaleBtn.layer.cornerRadius = 20;
femaleBtn.layer.masksToBounds = YES;
[femaleBtn setBackgroundImage:[UIImage imageNamed:@"gene_female"] forState:UIControlStateNormal];
[femaleBtn addTarget:self action:@selector(femaleClicked) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:femaleBtn];
femaleBtn.layer.borderColor = UIColor.whiteColor.CGColor;
[femaleBtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(contentLabel.mas_bottom).offset(20);
make.centerX.mas_equalTo(0);
make.height.mas_equalTo(40);
make.width.mas_equalTo(118);
}];
UIButton *maleBtn = [UIButton buttonWithType:UIButtonTypeCustom];
maleBtn.backgroundColor = [UIColor colorWithHex:0 alpha:.1];
maleBtn.layer.cornerRadius = 20;
maleBtn.layer.masksToBounds = YES;
[maleBtn setBackgroundImage:[UIImage imageNamed:@"gene_male"] forState:UIControlStateNormal];
[maleBtn addTarget:self action:@selector(maleClicked) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:maleBtn];
maleBtn.layer.borderColor = UIColor.whiteColor.CGColor;
[maleBtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(femaleBtn.mas_bottom).offset(14);
make.centerX.mas_equalTo(0);
make.height.mas_equalTo(40);
make.width.mas_equalTo(118);
}];
}
- (void)femaleClicked {
[self hide];
if ([self.selectDelegate respondsToSelector:@selector(didClickGender:)]) {
[Phobos track:@"on_click_button" attributes:@{@"page_name": @"face_scan", @"button_name": @"female",@"tab_name": @"AI面孔"}];
[self.selectDelegate didClickGender:0];
}
}
- (void)maleClicked {
[self hide];
if ([self.selectDelegate respondsToSelector:@selector(didClickGender:)]) {
[Phobos track:@"on_click_button" attributes:@{@"page_name": @"face_scan", @"button_name": @"male",@"tab_name": @"AI面孔"}];
[self.selectDelegate didClickGender:1];
}
}
- (void)hide {
self.hidden = YES;
}
- (void)didTapView {
}
@end
//
// GMMarkView.h
// Gengmei
//
// Created by 刘鹿杰 on 2019/6/28.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMFaceReplayModel.h"
NS_ASSUME_NONNULL_BEGIN
@interface GMMarkView : GMView
- (instancetype)initWithType:(GMFaceReplayModel *)replayModel;
@end
NS_ASSUME_NONNULL_END
//
// GMMarkView.m
// Gengmei
//
// Created by 刘鹿杰 on 2019/6/28.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMMarkView.h"
@interface GMMarkView()
@property (nonatomic, strong) GMFaceReplayModel *replayModel;
@end
@implementation GMMarkView
- (instancetype)initWithType:(GMFaceReplayModel *)replayModel {
if (self = [super init]) {
self.replayModel = replayModel;
[self setupUI];
}
return self;
}
- (void)setupUI {
// 画底线
NSArray *vLineArray = [NSArray arrayWithObjects:@140, @181, @354, @395, @568, @609, nil];
NSArray *hLineArray = [NSArray arrayWithObjects:@120, @161, @460, @501, @818, @859, @1178, @1219, nil];
for (int i = 0; i < 6; i++) {
GMView *vLineView = [[GMView alloc] init];
vLineView.backgroundColor = [UIColor colorWithWhite:1 alpha:0.16];
// 设置frame
CGFloat x = ([vLineArray[i] floatValue] / 750) * MAINSCREEN_WIDTH;
vLineView.frame = CGRectMake(x, 0, 0.5, MAINSCREEN_HEIGHT);
[self addSubview:vLineView];
}
for (int i = 0; i < 8; i++) {
GMView *hLineView = [[GMView alloc] init];
hLineView.backgroundColor = [UIColor colorWithWhite:1 alpha:0.16];
// 设置frame
CGFloat y = ([hLineArray[i] floatValue] / 1334) * MAINSCREEN_HEIGHT;
hLineView.frame = CGRectMake(0, y, MAINSCREEN_WIDTH, 0.5);
[self addSubview:hLineView];
}
// 设置圆圈背景图
UIImageView *leftImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"facev3_left_bgimage"]];
[self addSubview:leftImageView];
[leftImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.bottom.mas_equalTo(self.bottom);
make.left.mas_equalTo(self.left);
}];
UIImageView *rightImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"facev3_right_bgimage"]];
[self addSubview:rightImageView];
[rightImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.top);
make.right.mas_equalTo(self.right);
}];
UIImageView *iconImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"facev3_lookface"]];
[self addSubview:iconImageView];
[iconImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.bottom.mas_equalTo(self.bottom).offset(-(30 + GMView.safeAreaInsetsBottom));
make.right.mas_equalTo(self.right).offset(-15);
}];
if (self.replayModel.tapType == GMReplayViewTapPreview) {return;}
// 抖音视频录制 log 添加
UIImageView *logImageView = [[UIImageView alloc] init];
logImageView.backgroundColor = [UIColor clearColor];
[self addSubview:logImageView];
// 文字
GMLabel *logLabel = [[GMLabel alloc] init];
logLabel.numberOfLines = 2;
logLabel.layer.shadowColor = [UIColor blackColor].CGColor;
logLabel.layer.shadowOpacity = 0.1;
logLabel.layer.shadowRadius = 5;
NSString *tipString = @"下载更美APP\n获取脸型报告";
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
paragraphStyle.lineSpacing = self.replayModel.tapType == GMReplayViewTapWechat ? 8 : 0;
NSDictionary *attriDict = @{NSParagraphStyleAttributeName:paragraphStyle,NSFontAttributeName:[UIFont gmFont:14],NSForegroundColorAttributeName:UIColor.whiteColor};
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc]initWithString:tipString attributes:attriDict];
logLabel.attributedText = attributedString;
logLabel.textAlignment = NSTextAlignmentLeft;
[self addSubview:logLabel];
if (self.replayModel.tapType == GMReplayViewTapWechat) { // 分析到微信
[logImageView setImage:[UIImage imageNamed:@"icon"]];
[logImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.height.mas_equalTo(54);
make.bottom.mas_equalTo(self.mas_bottom).offset(-(30 + GMView.safeAreaInsetsBottom));
make.left.mas_equalTo(self.mas_left).offset(20);
}];
[logLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.mas_equalTo(logImageView.mas_centerY);
make.left.mas_equalTo(self.mas_left).offset(84);
make.width.mas_equalTo(84);
}];
}else{ // 抖音约束
logLabel.textAlignment = NSTextAlignmentCenter;
[logLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.bottom.mas_equalTo(self.mas_bottom).offset(-(30 + GMView.safeAreaInsetsBottom));
make.left.mas_equalTo(self.mas_left).offset(20);
make.width.mas_equalTo(84);
}];
[logImageView sd_setImageWithURL:[NSURL URLWithString:self.replayModel.dataEwm]];
[logImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.height.mas_equalTo(85);
make.bottom.mas_equalTo(logLabel.mas_top).offset(-10);
make.centerX.mas_equalTo(logLabel.mas_centerX);
}];
}
}
@end
//
// GMTitleView.h
// Gengmei
//
// Created by 刘鹿杰 on 2019/6/28.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
//typedef NS_ENUM(NSInteger,GMTitleViewType){
// TitleCountTypeOne = 0, // 只显示title
// TitleCountTypeTwo = 1, // 文字显示在顶部
// TitleCountTypeThirt = 2, // 文字显示在右侧
//};
@class GMFaceCommonSubtitleModel;
@interface GMTitleView : GMView
- (instancetype)initWithModel:(GMFaceCommonSubtitleModel *)model;
@end
NS_ASSUME_NONNULL_END
//
// GMTitleView.m
// Gengmei
//
// Created by 刘鹿杰 on 2019/6/28.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMTitleView.h"
#import "GMFaceV3Model.h"
@interface GMTitleView ()
@property (nonatomic, assign)GMFaceCommonSubtitleModel *tempModel;
@property (nonatomic, strong) GMLabel *titleLabel;
@property (nonatomic, strong) GMLabel *bestLabel;
@property (nonatomic, strong) GMLabel *contentLabel;
@end
@implementation GMTitleView
- (instancetype)initWithModel:(GMFaceCommonSubtitleModel *)model; {
if (self = [super init]) {
self.tempModel = model;
[self setup];
}
return self;
}
- (void)setup {
[super setup];
self.titleLabel = [GMLabel new];
self.titleLabel.numberOfLines = 2;
self.titleLabel.textColor = UIColor.whiteColor;
self.titleLabel.font = [UIFont gmBoldFont:15];
_titleLabel.text = self.tempModel.title;
_titleLabel.shadowColor = [UIColor colorWithHexString:@"000000" alpha:0.3];
_titleLabel.shadowOffset = CGSizeMake(0, 1);
_titleLabel.layer.shadowRadius = 0.5;
[self addSubview:_titleLabel];
if ([self.tempModel.best isNonEmpty]) {
// 最佳比例
self.bestLabel = [GMLabel labelWithTextColor:[UIColor colorWithHexString:@"00EFFF"] fontSize:9];
_bestLabel.text = self.tempModel.best;
_bestLabel.shadowColor = [UIColor colorWithHexString:@"000000" alpha:0.3];
_bestLabel.shadowOffset = CGSizeMake(0, 1);
_bestLabel.layer.shadowRadius = 0.5;
[self addSubview:_bestLabel];
}
if ([self.tempModel.content isNonEmpty]) {
// 描述
self.contentLabel = [GMLabel labelWithTextColor:UIColor.whiteColor fontSize:9];
_contentLabel.text = self.tempModel.content;
_contentLabel.shadowColor = [UIColor colorWithHexString:@"000000" alpha:0.3];
_contentLabel.shadowOffset = CGSizeMake(0, 1);
_contentLabel.layer.shadowRadius = 0.5;
[self addSubview:_contentLabel];
}
[self addConstraints];
}
- (void)addConstraints {
CGFloat titleLabelHight = 15;
if (self.tempModel.title.length > 6) {
titleLabelHight = 42;
}
[self.titleLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.top.mas_equalTo(self.top);
make.width.mas_equalTo(90);
make.height.mas_equalTo(titleLabelHight);
}];
if ([self.tempModel.best isNonEmpty]) {
[self.bestLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.left);
make.top.mas_equalTo(self.top).offset(12 + titleLabelHight);
make.width.mas_equalTo(120);
make.height.mas_equalTo(9);
}];
}
if ([self.tempModel.content isNonEmpty]) {
if ([self.tempModel.best isNonEmpty]) {
[self.contentLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.left);
make.top.mas_equalTo(self.top).offset(25 + titleLabelHight);
make.width.mas_equalTo(120);
make.height.mas_equalTo(9);
}];
}else{
[self.contentLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.left);
make.top.mas_equalTo(self.top).offset(8 + titleLabelHight);
make.width.mas_equalTo(120);
make.height.mas_equalTo(9);
}];
}
}
}
@end
//
// GMFaceSkinFrontView.swift
// Gengmei
//
// Created by 刘鹿杰 on 2019/11/8.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
import Foundation
import GMBaseSwift
class GMFaceSkinFrontView : GMView {
let margin = (Constant.screenHeight - (OCNavigationBar.barHeight + UIView.safeAreaInsetsBottom + Constant.screenWidth + 235)) / 2
let viewMaxHeight = Constant.screenHeight - 167 - 120 - OCNavigationBar.barHeight + UIView.safeAreaInsetsBottom
override func setup() {
super.setup()
self.backgroundColor = UIColor.clear
let hudView = GMView()
hudView.backgroundColor = UIColor.clear
self.addSubview(hudView)
hudView.snp.makeConstraints { (make) in
make.edges.equalToSuperview()
}
// 添加顶部View
self.addSubview(topView)
topView.snp.makeConstraints { (make) in
make.top.equalTo(OCNavigationBar.barHeight + 10)
make.right.equalTo(-16)
make.left.equalTo(16)
make.height.equalTo(30)
}
self.addSubview(noticeLabel)
noticeLabel.snp.makeConstraints { (make) in
make.size.equalTo(CGSize(width: 300, height: 35))
make.bottom.equalToSuperview().offset(-187 - UIView.safeAreaInsetsBottom)
make.centerX.equalTo(self)
}
let minMargin = margin < 10 ? 10 : margin
// 添加图片
let lefticon = UIImageView(image: UIImage(named: "face_skin_front_lefticon"))
self.addSubview(lefticon)
lefticon.snp.makeConstraints { (make) in
make.top.equalTo(topView.snp.bottom).offset(minMargin)
make.left.equalTo(16)
make.height.equalTo(67)
}
let righticon = UIImageView(image: UIImage(named: "face_skin_front_righticon"))
self.addSubview(righticon)
righticon.snp.makeConstraints { (make) in
make.height.equalTo(77)
make.right.equalTo(-16)
make.bottom.equalTo(noticeLabel.snp.top).offset(-minMargin)
}
let frontImage = UIImageView(image: UIImage(named: "face_skin_front_image"))
self.addSubview(frontImage)
let frontImageHeight = (viewMaxHeight < Constant.screenWidth - 32 ) ? viewMaxHeight : Constant.screenWidth - 32
frontImage.snp.makeConstraints { (make) in
make.top.equalTo(topView.snp.bottom).offset(minMargin)
make.width.height.equalTo(frontImageHeight)
make.centerX.equalToSuperview()
}
self.addSubview(closeEyeLabel)
closeEyeLabel.snp.makeConstraints { (make) in
make.centerX.equalTo(frontImage.snp.centerX)
make.centerY.equalTo(frontImage.snp.centerY).offset(-60)
}
}
lazy var flashLaber : GMLabel = {
let flashLaber = GMLabel(textAlignment: .left, backgroundColor: .clear, textColor: UIColor(hex: 0xD8D8D8).alpha(1.0), fontSize: 12)
flashLaber.text = "闪光灯【已开启】"
flashLaber.layer.shadowColor = UIColor.black.cgColor
flashLaber.layer.shadowOpacity = 0.1
flashLaber.layer.shadowRadius = 5
return flashLaber
}()
lazy var middleLabel : GMLabel = {
let middleLabel = GMLabel(textAlignment: .left, backgroundColor: .clear, textColor: UIColor(hex: 0xD8D8D8).alpha(1.0), fontSize: 12)
middleLabel.text = "直视摄像头"
middleLabel.layer.shadowColor = UIColor.black.cgColor
middleLabel.layer.shadowOpacity = 0.1
middleLabel.layer.shadowRadius = 5
return middleLabel
}()
lazy var facePlaceLabel : GMLabel = {
let facePlaceLabel = GMLabel(textAlignment: .left, backgroundColor: .clear, textColor: UIColor(hex: 0xD8D8D8), fontSize: 12)
facePlaceLabel.text = "脸部位置"
facePlaceLabel.layer.shadowColor = UIColor.black.cgColor
facePlaceLabel.layer.shadowOpacity = 0.1
facePlaceLabel.layer.shadowRadius = 5
return facePlaceLabel
}()
lazy var tipsLaber : GMLabel = {
let tipsLaber = GMLabel(textAlignment: .left, backgroundColor: .clear, textColor: UIColor(hex: 0xD8D8D8), fontSize: 12)
tipsLaber.text = "【近一点】"
tipsLaber.layer.shadowColor = UIColor.black.cgColor
tipsLaber.layer.shadowOpacity = 0.1
tipsLaber.layer.shadowRadius = 5
return tipsLaber
}()
lazy var closeEyeLabel : GMLabel = {
let closeEyeLabel = GMLabel(textAlignment: .left, backgroundColor: .clear, textColor: UIColor(hex: 0xD8D8D8), fontSize: 18)
closeEyeLabel.text = "请闭眼"
closeEyeLabel.isHidden = true
closeEyeLabel.layer.shadowColor = UIColor(hex: 0x50D7E7).cgColor
closeEyeLabel.layer.shadowOpacity = 0.1
closeEyeLabel.layer.shadowRadius = 5
return closeEyeLabel
}()
lazy var topView: GMView = {
let topView = GMView()
topView.backgroundColor = UIColor.clear
topView.addSubview(flashLaber) // 闪光灯
topView.addSubview(middleLabel) // 直视摄像头
topView.addSubview(tipsLaber) // 【近一点】【远一点】【良好】
topView.addSubview(facePlaceLabel) // 脸部位置
flashLaber.snp.makeConstraints({ (make) in
make.centerY.equalTo(topView.snp.centerY)
make.left.equalTo(0)
});
middleLabel.snp.makeConstraints({ (make) in
make.centerY.equalTo(topView.snp.centerY)
make.centerX.equalTo(topView.snp.centerX)
});
tipsLaber.snp.makeConstraints({ (make) in
make.centerY.equalTo(topView.snp.centerY)
make.right.equalTo(0)
make.width.equalTo(75)
});
facePlaceLabel.snp.makeConstraints({ (make) in
make.centerY.equalTo(topView.snp.centerY)
make.right.equalTo(-75)
});
return topView
}()
// 懒加载 懒加载说明文字
lazy var noticeLabel: GMLabel = {
let noticeLabel = GMLabel(textAlignment: .left, backgroundColor: .clear, textColor: .white, fontSize: 14)
let text = " · 摘下眼镜,平视摄像头,寻找合适的距离\n"
let attributedStr = NSMutableAttributedString(string: text)
let style = NSMutableParagraphStyle()
style.lineSpacing = 1
attributedStr.addAttribute(NSAttributedString.Key.paragraphStyle, value: style, range: NSMakeRange(0, attributedStr.length))
noticeLabel.attributedText = attributedStr
noticeLabel.verticalAlignment = .middle
noticeLabel.layer.shadowColor = UIColor.black.cgColor
noticeLabel.layer.shadowOpacity = 0.1
noticeLabel.layer.shadowRadius = 5
noticeLabel.layer.cornerRadius = 4;
noticeLabel.layer.masksToBounds = true
noticeLabel.numberOfLines = 0;
noticeLabel.backgroundColor = UIColor(hex: 0, alpha: 0.25)
return noticeLabel
}()
}
//
// GMFaceSkinView.swift
// Gengmei
//
// Created by 刘鹿杰 on 2019/8/17.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
import Foundation
import GMBaseSwift
class GMFaceSkinView : GMView {
override func setup() {
super.setup()
let hudView = GMView()
hudView.backgroundColor = UIColor(hex: 0, alpha: 0.5)
self.addSubview(hudView)
hudView.snp.makeConstraints { (make) in
make.edges.equalToSuperview()
}
let noticeHudView = UIImageView(image: UIImage(named: "face_notice_hud"))
self.addSubview(noticeHudView)
noticeHudView.snp.makeConstraints { (make) in
make.bottom.equalToSuperview().offset(-160 - UIView.safeAreaInsetsBottom)
make.left.right.equalToSuperview()
make.height.equalTo(144)
}
self.addSubview(noticeLabel)
noticeLabel.snp.makeConstraints { (make) in
make.size.equalTo(CGSize(width: 237, height: 104))
make.bottom.equalToSuperview().offset(-187 - UIView.safeAreaInsetsBottom)
make.centerX.equalTo(self)
}
}
// 懒加载 懒加载说明文字
lazy var noticeLabel: GMLabel = {
let noticeLabel = GMLabel(textAlignment: .left, backgroundColor: .clear, textColor: .white, fontSize: 14)
let text = " · 请平视后置摄像头寻找合适距离\n · 请跟随人声引导作出相应动作\n · 听到闭眼提示后,请闭上眼睛 \n · 此时相机会自动拍照哦"
let attributedStr = NSMutableAttributedString(string: text)
let style = NSMutableParagraphStyle()
style.lineSpacing = 2
attributedStr.addAttribute(NSAttributedString.Key.paragraphStyle, value: style, range: NSMakeRange(0, attributedStr.length))
noticeLabel.attributedText = attributedStr
noticeLabel.verticalAlignment = .middle
noticeLabel.layer.shadowColor = UIColor.black.cgColor
noticeLabel.layer.shadowOpacity = 0.1
noticeLabel.layer.shadowRadius = 5
noticeLabel.layer.cornerRadius = 4;
noticeLabel.layer.masksToBounds = true
noticeLabel.numberOfLines = 0;
noticeLabel.backgroundColor = UIColor(hex: 0, alpha: 0.25)
return noticeLabel
}()
}
//
// GMSkinGuidancePopView.h
// Gengmei
//
// Created by 刘鹿杰 on 2019/8/16.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMPopupBgView.h"
@protocol GMSkinGuidancePopViewDelegate <NSObject>
-(void)guidanceBtnClosePopView;
@end
NS_ASSUME_NONNULL_BEGIN
@interface GMSkinGuidancePopView : GMPopupBgView
@property (nonatomic, weak) id <GMSkinGuidancePopViewDelegate> guidanceDelegate;
@end
NS_ASSUME_NONNULL_END
//
// GMSkinGuidancePopView.m
// Gengmei
//
// Created by 刘鹿杰 on 2019/8/16.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMSkinGuidancePopView.h"
@implementation GMSkinGuidancePopView
-(void)setup{
[super setup];
self.animationType = GMPopupAnimationTypeFade;
// 设置 container
self.container.backgroundColor = [UIColor colorWithHex:000000 alpha:0.5];// [UIColor clearColor];
[self.container mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self);
}];
// imageView
GMImageView *imageView = [[GMImageView alloc] initWithImage:[UIImage imageNamed:@"face_skin_lead"]];
[self.container addSubview:imageView];
[imageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.equalTo(self.container.mas_right).offset(-4);
make.top.mas_equalTo(OCNavigationBar.barHeight + 25);
}];
// imageView1
UIImage *image1 = [UIImage imageNamed:@"face_skin_lead1"];
GMImageView *imageView1 = [[GMImageView alloc] initWithImage:image1];
[self.container addSubview:imageView1];
CGFloat left = MAINSCREEN_WIDTH * 0.25 - image1.size.width / 2 - 12;
[imageView1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(left);
make.bottom.mas_equalTo(- (54 + UIView.safeAreaInsetsBottom));
}];
// 添加点击效果
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(hideView)];
[self addGestureRecognizer:tap];
}
- (void)hideView {
[self hide];
if (self.guidanceDelegate && [self.guidanceDelegate respondsToSelector:@selector(guidanceBtnClosePopView)]) {
[self.guidanceDelegate guidanceBtnClosePopView];
}
}
@end
//
// GMSkinUpLoadHeadPicPopView.h
// Gengmei
//
// Created by 刘鹿杰 on 2019/8/29.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMPopupBgView.h"
typedef void(^SkinUpLoadPicPopView)();
NS_ASSUME_NONNULL_BEGIN
@interface GMSkinUpLoadHeadPicPopView : GMPopupBgView
@property (nonatomic, copy)SkinUpLoadPicPopView popViewBlock;
@end
NS_ASSUME_NONNULL_END
//
// GMSkinUpLoadHeadPicPopView.m
// Gengmei
//
// Created by 刘鹿杰 on 2019/8/29.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMSkinUpLoadHeadPicPopView.h"
#define containerHeight ((MAINSCREEN_WIDTH - 105) * 322 / 270)
@implementation GMSkinUpLoadHeadPicPopView
-(void)setup{
[super setup];
self.animationType = GMPopupAnimationTypeFade;
self.disableCancelTouch = YES;
self.container.backgroundColor = [UIColor clearColor];
[self.container mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(CGSizeMake(MAINSCREEN_WIDTH - 105, containerHeight));
make.centerX.equalTo(self.mas_centerX);
make.centerY.equalTo(self.mas_centerY);
}];
// 添加View
GMView *contentView = [[GMView alloc] init];
contentView.backgroundColor = [UIColor whiteColor];
contentView.layer.cornerRadius = 10;
contentView.clipsToBounds = YES;
[self.container addSubview:contentView];
[contentView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.container.mas_top).offset(40);
make.width.equalTo(self.container.mas_width);
make.centerX.equalTo(self.mas_centerX);
make.height.mas_equalTo(containerHeight - 95);
}];
// imageView
GMImageView *imageView = [[GMImageView alloc] initWithImage:[UIImage imageNamed:@"face_skin_headIcon"]];
[self.container addSubview:imageView];
[imageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.container.mas_top);
make.centerX.equalTo(self.mas_centerX);
}];
// 添加描述label
GMLabel *textlabel = [[GMLabel alloc] init];
textlabel.numberOfLines = 2;
NSString *tipString = @"上传头像,炫耀肌龄\n 请上传你美美的照片作为头像";
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
paragraphStyle.lineSpacing = 8;
NSDictionary *attriDict = @{NSParagraphStyleAttributeName:paragraphStyle,NSFontAttributeName:[UIFont gmFont:15],NSForegroundColorAttributeName:UIColor.headlineText};
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc]initWithString:tipString attributes:attriDict];
textlabel.attributedText = attributedString;
textlabel.textAlignment = NSTextAlignmentCenter;
[contentView addSubview:textlabel];
[textlabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(contentView.mas_centerX);
make.centerY.equalTo(contentView.mas_centerY);
}];
// 添加btn
GMButton *btn = [[GMButton alloc] init];
[contentView addSubview:btn];
[btn addTarget:self action:@selector(btnClick) forControlEvents:UIControlEventTouchUpInside];
UIImage *btnImage = [[UIImage imageNamed:@"face_startActicn_btn"] resizableImageWithCapInsets:UIEdgeInsetsMake(20, 100, 20, 100) resizingMode:UIImageResizingModeStretch];
[btn setBackgroundImage:btnImage forState:UIControlStateNormal];
[btn setTitleColor:UIColor.whiteColor forState:UIControlStateNormal];
[btn.titleLabel setFont:[UIFont gmLightFont:16]];
[btn setTitle:@"上传" forState:UIControlStateNormal];
[btn mas_makeConstraints:^(MASConstraintMaker *make) {
make.bottom.equalTo(contentView.mas_bottom).offset(-23);
make.centerX.equalTo(contentView.mas_centerX);
make.size.mas_equalTo(CGSizeMake(200, 40));
}];
// 添加底部关闭按钮
UIImageView *closeIcon = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"sign_rule_close"]];
closeIcon.userInteractionEnabled = YES;
[self.container addSubview:closeIcon];
// 添加点击效果
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(hideView)];
[closeIcon addGestureRecognizer:tap];
[closeIcon mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(contentView.mas_centerX);
make.top.equalTo(contentView.mas_bottom).offset(20);
make.size.mas_equalTo(CGSizeMake(35, 35));
}];
}
-(void)hideView{
[self hide];
}
- (void)btnClick {
[self hide];
if (self.popViewBlock) {
self.popViewBlock();
}
}
@end
//
// GMTestSkinTeachPopView.h
// Gengmei
//
// Created by 刘鹿杰 on 2019/8/16.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMPopupBgView.h"
NS_ASSUME_NONNULL_BEGIN
@interface GMTestSkinTeachPopView : GMPopupBgView
@end
NS_ASSUME_NONNULL_END
//
// GMTestSkinTeachPopView.m
// Gengmei
//
// Created by 刘鹿杰 on 2019/8/16.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMTestSkinTeachPopView.h"
#import "GMTestSkinTeachVC.h"
#define containerHeight ((MAINSCREEN_WIDTH - 105) * 249 / 270)
@interface GMTestSkinTeachPopView()
@end
@implementation GMTestSkinTeachPopView
-(void)setup{
[super setup];
self.animationType = GMPopupAnimationTypeFade;
self.disableCancelTouch = YES;
// 设置 container
self.container.backgroundColor = [UIColor clearColor];
[self.container mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(CGSizeMake(MAINSCREEN_WIDTH - 105, containerHeight));
make.centerX.equalTo(self.mas_centerX);
make.centerY.equalTo(self.mas_centerY);
}];
// 添加View
GMView *contentView = [[GMView alloc] init];
contentView.backgroundColor = [UIColor whiteColor];
contentView.layer.cornerRadius = 10;
contentView.clipsToBounds = YES;
[self.container addSubview:contentView];
[contentView mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.equalTo(self.container.mas_width);
make.top.equalTo(self.container.mas_top).offset(35);
make.centerX.equalTo(self.mas_centerX);
make.height.mas_equalTo(containerHeight - 35);
}];
// imageView
GMImageView *imageView = [[GMImageView alloc] initWithImage:[UIImage imageNamed:@"face_skin_popIcon"]];
[self.container addSubview:imageView];
[imageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.container.mas_top);
make.centerX.equalTo(self.mas_centerX);
}];
// 添加描述label
GMLabel *textlabel = [[GMLabel alloc] init];
textlabel.numberOfLines = 2;
NSString *tipString = @"检测肤质使用摄像头自动拍照\n请认真看教程哦";
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
paragraphStyle.lineSpacing = 8;
NSDictionary *attriDict = @{NSParagraphStyleAttributeName:paragraphStyle,NSFontAttributeName:[UIFont gmFont:15],NSForegroundColorAttributeName:UIColor.headlineText};
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc]initWithString:tipString attributes:attriDict];
textlabel.attributedText = attributedString;
textlabel.textAlignment = NSTextAlignmentCenter;
[contentView addSubview:textlabel];
[textlabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(contentView.mas_centerX);
make.centerY.equalTo(contentView.mas_centerY);
}];
// 添加btn
GMButton *btn = [[GMButton alloc] init];
[contentView addSubview:btn];
[btn addTarget:self action:@selector(btnClick) forControlEvents:UIControlEventTouchUpInside];
UIImage *image = [[UIImage imageNamed:@"face_startActicn_btn"] resizableImageWithCapInsets:UIEdgeInsetsMake(20, 100, 20, 100) resizingMode:UIImageResizingModeStretch];
[btn setBackgroundImage:image forState:UIControlStateNormal];
[btn setTitleColor:UIColor.whiteColor forState:UIControlStateNormal];
[btn.titleLabel setFont:[UIFont gmLightFont:16]];
[btn setTitle:@"知道啦" forState:UIControlStateNormal];
[btn mas_makeConstraints:^(MASConstraintMaker *make) {
make.bottom.equalTo(contentView.mas_bottom).offset(-23);
make.centerX.equalTo(contentView.mas_centerX);
make.size.mas_equalTo(CGSizeMake(200, 40));
}];
}
- (void)btnClick {
[UIView animateWithDuration:self.animationDuration animations:^{
self.backgroundColor = [UIColor colorWithWhite:0 alpha:0];
} completion:^(BOOL finished) {
[self removeFromSuperview];
GMTestSkinTeachVC *testSkinVc = [[GMTestSkinTeachVC alloc] init];
[AppDelegate.navigation pushViewController:testSkinVc animated:YES];
}];
}
@end
//
// GMTestSkinTeachVC.h
// Gengmei
//
// Created by 刘鹿杰 on 2019/8/14.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@interface GMTestSkinTeachVC : WMBaseViewController
@end
NS_ASSUME_NONNULL_END
//
// GMTestSkinTeachVC.m
// Gengmei
//
// Created by 刘鹿杰 on 2019/8/14.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMTestSkinTeachVC.h"
@interface GMTestSkinTeachVC ()
@end
@implementation GMTestSkinTeachVC
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"如何测的更准";
self.navigationBar.backgroundColor = UIColor.whiteColor;
self.view.backgroundColor = UIColor.whiteColor;
GMScrollView *scrollView = [[GMScrollView alloc] init];
scrollView.showsVerticalScrollIndicator = NO;
[self.view addSubview:scrollView];
[scrollView mas_updateConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view);
make.top.mas_equalTo(OCNavigationBar.barHeight);
}];
// 创建containerView
UIView * container = [[UIView alloc] init];
[scrollView addSubview:container];
[container mas_makeConstraints:^(MASConstraintMaker *make){
make.edges.equalTo(scrollView);
make.width.equalTo(scrollView);
}];
// 添加展示图片
UIImage *image = [UIImage imageNamed:@"face_skin_teachImage"];
CGFloat imageViewHeight = ((MAINSCREEN_WIDTH - 55) * image.size.height) / image.size.width;
GMImageView *picImageView = [[GMImageView alloc] initWithImage:image];
[scrollView addSubview:picImageView];
[picImageView mas_updateConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(20);
make.top.mas_equalTo(36);
make.right.mas_equalTo(-35);
make.height.mas_equalTo(imageViewHeight);
}];
// 添加按钮View
UIView *btnBgView = [[GMView alloc] init];
btnBgView.backgroundColor = UIColor.whiteColor;
[scrollView addSubview:btnBgView];
[btnBgView mas_updateConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(picImageView.mas_bottom);
make.left.right.mas_equalTo(0);
make.height.mas_equalTo(112);
make.bottom.mas_equalTo(container.mas_bottom);
}];
// 设置点击拍照button
GMButton *startBtn = [[GMButton alloc] init];
[btnBgView addSubview:startBtn];
[startBtn mas_updateConstraints:^(MASConstraintMaker *make) {
make.centerX.mas_equalTo(btnBgView.mas_centerX);
make.centerY.mas_equalTo(btnBgView.mas_centerY);
make.width.mas_equalTo(160);
make.height.mas_equalTo(40);
}];
UIImage *startBtnImage = [[UIImage imageNamed:@"face_startActicn_btn"] resizableImageWithCapInsets:UIEdgeInsetsMake(20, 80, 20, 80) resizingMode:UIImageResizingModeStretch];
[startBtn setBackgroundImage:startBtnImage forState:UIControlStateNormal];
[startBtn setTitle:@"开始拍照" forState:UIControlStateNormal];
[startBtn setTitleColor:UIColor.whiteColor forState:UIControlStateNormal];
[startBtn.titleLabel setFont:[UIFont systemFontOfSize:14]];
[startBtn addTarget:self action:@selector(startTestBtnClick) forControlEvents:UIControlEventTouchUpInside];
// 设置scrollView
scrollView.contentSize = CGSizeMake(0, imageViewHeight + 148);
}
// 开始检测按钮
- (void)startTestBtnClick {
[self.navigationController popViewControllerAnimated:YES];
}
@end
//
// GMVoiceSwitchView.swift
// Gengmei
//
// Created by 刘鹿杰 on 2019/11/12.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
import Foundation
protocol GMVoiceSwitchViewDelegate : NSObjectProtocol {
func switchViewDidSelected(isON: Bool)
}
class GMVoiceSwitchView : GMView {
weak var delegate: GMVoiceSwitchViewDelegate?
override func setup() {
super.setup()
// 语音开关
self.backgroundColor = UIColor.clear
self.addSubview(voiceSwitch)
voiceSwitch.snp.makeConstraints { (make) in
make.top.right.equalTo(0)
make.size.equalTo(CGSize(width: 48, height: 27))
}
// 语音开关描述文字
self.addSubview(voiceLabel)
voiceLabel.snp.makeConstraints({ (make) in
make.top.equalTo(voiceSwitch.snp_bottom).offset(10)
make.right.equalTo(0)
});
voiceSwitch.isOn = !UserDefaults.standard.bool(forKey: "noHumanVoice")
if UserDefaults.standard.bool(forKey: "noHumanVoice") {
voiceLabel.text = "人声引导:关"
}else {
voiceLabel.text = "人声引导:开"
}
}
// 懒加载人声引导提示
lazy var voiceLabel : GMLabel = {
let voiceLabel = GMLabel(textAlignment: .right, backgroundColor: .clear, textColor: .white, fontSize: 10)
voiceLabel.layer.shadowColor = UIColor.black.cgColor
voiceLabel.layer.shadowOpacity = 0.1
voiceLabel.layer.shadowRadius = 5
return voiceLabel
}()
@objc func switchAction(sender:UISwitch){
if sender.isOn {
UserDefaults.standard.set(false, forKey: "noHumanVoice")
voiceLabel.text = "人声引导:开"
}else {
UserDefaults.standard.set(true, forKey: "noHumanVoice")
voiceLabel.text = "人声引导:关"
}
Phobos.track("on_click_button", attributes:["page_name": "face_scan","tab_name" : "AI测肤","button_name" : sender.isOn ? "guide_voice_on" : "guide_voice_close"])
if delegate != nil { // 及时关闭声音
delegate?.switchViewDidSelected(isON: sender.isOn)
}
}
// MARK: - 懒加载控件
// 懒加载人声引导开关switch
lazy var voiceSwitch : UISwitch = {
let voiceSwitch = UISwitch()
voiceSwitch.onTintColor = UIColor(hex: 0x45E7DE).alpha(0.7) //设置开启状态显示的颜色
voiceSwitch.tintColor = UIColor(white: 1.0, alpha: 0.5) //设置关闭状态的颜色
voiceSwitch.thumbTintColor = UIColor.white //滑块上小圆点的颜色
voiceSwitch.isOn = true
voiceSwitch.addTarget(self, action: #selector(GMVoiceSwitchView.switchAction), for: .valueChanged)
return voiceSwitch
}()
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment