├── .gitignore ├── demo.png ├── Assets ├── More.png ├── album.png ├── More@2x.png ├── More@3x.png ├── album@2x.png ├── album@3x.png ├── photos.jpg ├── trigger.png ├── trigger@2x.png └── trigger@3x.png ├── FaceApp ├── Assets.xcassets │ ├── Contents.json │ ├── sad.imageset │ │ ├── sad.png │ │ └── Contents.json │ ├── face.imageset │ │ ├── face.jpg │ │ └── Contents.json │ ├── fear.imageset │ │ ├── fear.png │ │ └── Contents.json │ ├── more.imageset │ │ ├── More.png │ │ ├── More@2x.png │ │ ├── More@3x.png │ │ └── Contents.json │ ├── album.imageset │ │ ├── album.png │ │ ├── album@2x.png │ │ ├── album@3x.png │ │ └── Contents.json │ ├── anger.imageset │ │ ├── anger.png │ │ └── Contents.json │ ├── happy.imageset │ │ ├── happy.png │ │ └── Contents.json │ ├── sjtuicon.imageset │ │ ├── 校标-校徽.png │ │ ├── 校标-校徽-1.png │ │ ├── 校标-校徽-2.png │ │ └── Contents.json │ ├── disgust.imageset │ │ ├── disgust.png │ │ └── Contents.json │ ├── neutral.imageset │ │ ├── neutral.png │ │ └── Contents.json │ ├── surprise.imageset │ │ ├── suprise.png │ │ └── Contents.json │ ├── trigger.imageset │ │ ├── trigger.png │ │ ├── trigger@2x.png │ │ ├── trigger@3x.png │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── 246x0w-2.jpg │ │ └── Contents.json │ └── captcha.imageset │ │ ├── 1528083146.jpg │ │ └── Contents.json ├── MyCollectionViewCell.swift ├── Base.lproj │ ├── Main.storyboard │ └── LaunchScreen.storyboard ├── Info.plist ├── AppDelegate.swift ├── HorizontalViewController.swift ├── TableViewController.swift ├── PreviewView.swift ├── MainViewController.swift ├── LoginViewController.swift ├── ProcessViewController.swift └── ViewController.swift ├── FaceApp.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcuserdata │ └── Arco.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── project.pbxproj ├── Podfile ├── README.md ├── FaceAppTests ├── Info.plist └── FaceAppTests.swift └── FaceAppUITests ├── Info.plist └── FaceAppUITests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | *.xcodeproj 2 | *.xcworkspace 3 | Pods/ 4 | *.lock 5 | -------------------------------------------------------------------------------- /demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebornzeroj/FaceApp/HEAD/demo.png -------------------------------------------------------------------------------- /Assets/More.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebornzeroj/FaceApp/HEAD/Assets/More.png -------------------------------------------------------------------------------- /Assets/album.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebornzeroj/FaceApp/HEAD/Assets/album.png -------------------------------------------------------------------------------- /Assets/More@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebornzeroj/FaceApp/HEAD/Assets/More@2x.png -------------------------------------------------------------------------------- /Assets/More@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebornzeroj/FaceApp/HEAD/Assets/More@3x.png -------------------------------------------------------------------------------- /Assets/album@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebornzeroj/FaceApp/HEAD/Assets/album@2x.png -------------------------------------------------------------------------------- /Assets/album@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebornzeroj/FaceApp/HEAD/Assets/album@3x.png -------------------------------------------------------------------------------- /Assets/photos.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebornzeroj/FaceApp/HEAD/Assets/photos.jpg -------------------------------------------------------------------------------- /Assets/trigger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebornzeroj/FaceApp/HEAD/Assets/trigger.png -------------------------------------------------------------------------------- /Assets/trigger@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebornzeroj/FaceApp/HEAD/Assets/trigger@2x.png -------------------------------------------------------------------------------- /Assets/trigger@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebornzeroj/FaceApp/HEAD/Assets/trigger@3x.png -------------------------------------------------------------------------------- /FaceApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /FaceApp/Assets.xcassets/sad.imageset/sad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebornzeroj/FaceApp/HEAD/FaceApp/Assets.xcassets/sad.imageset/sad.png -------------------------------------------------------------------------------- /FaceApp/Assets.xcassets/face.imageset/face.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebornzeroj/FaceApp/HEAD/FaceApp/Assets.xcassets/face.imageset/face.jpg -------------------------------------------------------------------------------- /FaceApp/Assets.xcassets/fear.imageset/fear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebornzeroj/FaceApp/HEAD/FaceApp/Assets.xcassets/fear.imageset/fear.png -------------------------------------------------------------------------------- /FaceApp/Assets.xcassets/more.imageset/More.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebornzeroj/FaceApp/HEAD/FaceApp/Assets.xcassets/more.imageset/More.png -------------------------------------------------------------------------------- /FaceApp/Assets.xcassets/album.imageset/album.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebornzeroj/FaceApp/HEAD/FaceApp/Assets.xcassets/album.imageset/album.png -------------------------------------------------------------------------------- /FaceApp/Assets.xcassets/anger.imageset/anger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebornzeroj/FaceApp/HEAD/FaceApp/Assets.xcassets/anger.imageset/anger.png -------------------------------------------------------------------------------- /FaceApp/Assets.xcassets/happy.imageset/happy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebornzeroj/FaceApp/HEAD/FaceApp/Assets.xcassets/happy.imageset/happy.png -------------------------------------------------------------------------------- /FaceApp/Assets.xcassets/more.imageset/More@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebornzeroj/FaceApp/HEAD/FaceApp/Assets.xcassets/more.imageset/More@2x.png -------------------------------------------------------------------------------- /FaceApp/Assets.xcassets/more.imageset/More@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebornzeroj/FaceApp/HEAD/FaceApp/Assets.xcassets/more.imageset/More@3x.png -------------------------------------------------------------------------------- /FaceApp/Assets.xcassets/album.imageset/album@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebornzeroj/FaceApp/HEAD/FaceApp/Assets.xcassets/album.imageset/album@2x.png -------------------------------------------------------------------------------- /FaceApp/Assets.xcassets/album.imageset/album@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebornzeroj/FaceApp/HEAD/FaceApp/Assets.xcassets/album.imageset/album@3x.png -------------------------------------------------------------------------------- /FaceApp/Assets.xcassets/sjtuicon.imageset/校标-校徽.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebornzeroj/FaceApp/HEAD/FaceApp/Assets.xcassets/sjtuicon.imageset/校标-校徽.png -------------------------------------------------------------------------------- /FaceApp/Assets.xcassets/disgust.imageset/disgust.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebornzeroj/FaceApp/HEAD/FaceApp/Assets.xcassets/disgust.imageset/disgust.png -------------------------------------------------------------------------------- /FaceApp/Assets.xcassets/neutral.imageset/neutral.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebornzeroj/FaceApp/HEAD/FaceApp/Assets.xcassets/neutral.imageset/neutral.png -------------------------------------------------------------------------------- /FaceApp/Assets.xcassets/sjtuicon.imageset/校标-校徽-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebornzeroj/FaceApp/HEAD/FaceApp/Assets.xcassets/sjtuicon.imageset/校标-校徽-1.png -------------------------------------------------------------------------------- /FaceApp/Assets.xcassets/sjtuicon.imageset/校标-校徽-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebornzeroj/FaceApp/HEAD/FaceApp/Assets.xcassets/sjtuicon.imageset/校标-校徽-2.png -------------------------------------------------------------------------------- /FaceApp/Assets.xcassets/surprise.imageset/suprise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebornzeroj/FaceApp/HEAD/FaceApp/Assets.xcassets/surprise.imageset/suprise.png -------------------------------------------------------------------------------- /FaceApp/Assets.xcassets/trigger.imageset/trigger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebornzeroj/FaceApp/HEAD/FaceApp/Assets.xcassets/trigger.imageset/trigger.png -------------------------------------------------------------------------------- /FaceApp/Assets.xcassets/AppIcon.appiconset/246x0w-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebornzeroj/FaceApp/HEAD/FaceApp/Assets.xcassets/AppIcon.appiconset/246x0w-2.jpg -------------------------------------------------------------------------------- /FaceApp/Assets.xcassets/captcha.imageset/1528083146.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebornzeroj/FaceApp/HEAD/FaceApp/Assets.xcassets/captcha.imageset/1528083146.jpg -------------------------------------------------------------------------------- /FaceApp/Assets.xcassets/trigger.imageset/trigger@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebornzeroj/FaceApp/HEAD/FaceApp/Assets.xcassets/trigger.imageset/trigger@2x.png -------------------------------------------------------------------------------- /FaceApp/Assets.xcassets/trigger.imageset/trigger@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebornzeroj/FaceApp/HEAD/FaceApp/Assets.xcassets/trigger.imageset/trigger@3x.png -------------------------------------------------------------------------------- /FaceApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | platform :ios, '10.0' 3 | use_frameworks! 4 | 5 | target 'FaceApp' do 6 | pod 'SnapKit', '~> 4.0.0' 7 | pod 'Alamofire', '~> 4.7' 8 | pod 'SwiftyJSON', '~> 4.0' 9 | pod 'Toast-Swift', '~> 3.0.1' 10 | end 11 | -------------------------------------------------------------------------------- /FaceApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /FaceApp/Assets.xcassets/face.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "face.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /FaceApp/Assets.xcassets/fear.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "fear.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /FaceApp/Assets.xcassets/sad.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "sad.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FaceApp 2 | 3 | Transform your face using Artificial Intelligence. 4 | 5 | ![demo](https://github.com/rebornzeroj/FaceApp/blob/master/demo.png) 6 | 7 | ## Pod 8 | 1. SnapKit 9 | 2. Alamofire 10 | 11 | ## Description 12 | I train a [CycleGAN](https://github.com/hardikbansal/CycleGAN) to change face. The GAN model is deployed on a http server. The mobile app upload a pic and get a response for transformed face. 13 | 14 | -------------------------------------------------------------------------------- /FaceApp/Assets.xcassets/anger.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "anger.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /FaceApp/Assets.xcassets/happy.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "happy.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /FaceApp/Assets.xcassets/captcha.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "1528083146.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /FaceApp/Assets.xcassets/disgust.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "disgust.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /FaceApp/Assets.xcassets/neutral.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "neutral.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /FaceApp/Assets.xcassets/surprise.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "suprise.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /FaceApp/Assets.xcassets/more.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "More.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "More@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "More@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /FaceApp/Assets.xcassets/album.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "album.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "album@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "album@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /FaceApp/Assets.xcassets/sjtuicon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "校标-校徽-2.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "校标-校徽.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "校标-校徽-1.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /FaceApp/Assets.xcassets/trigger.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "trigger.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "trigger@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "trigger@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /FaceAppTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /FaceAppUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /FaceApp.xcodeproj/xcuserdata/Arco.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | FaceApp.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 2D77DC0F209F56670022BAAB 16 | 17 | primary 18 | 19 | 20 | 2D77DC23209F56680022BAAB 21 | 22 | primary 23 | 24 | 25 | 2D77DC2E209F56680022BAAB 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /FaceAppTests/FaceAppTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FaceAppTests.swift 3 | // FaceAppTests 4 | // 5 | // Created by Arco on 2018/5/6. 6 | // Copyright © 2018 c. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import FaceApp 11 | 12 | class FaceAppTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /FaceAppUITests/FaceAppUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FaceAppUITests.swift 3 | // FaceAppUITests 4 | // 5 | // Created by Arco on 2018/5/6. 6 | // Copyright © 2018 c. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class FaceAppUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | 18 | // In UI tests it is usually best to stop immediately when a failure occurs. 19 | continueAfterFailure = false 20 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 21 | XCUIApplication().launch() 22 | 23 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 24 | } 25 | 26 | override func tearDown() { 27 | // Put teardown code here. This method is called after the invocation of each test method in the class. 28 | super.tearDown() 29 | } 30 | 31 | func testExample() { 32 | // Use recording to get started writing UI tests. 33 | // Use XCTAssert and related functions to verify your tests produce the correct results. 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /FaceApp/MyCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MyCollectionViewCell.swift 3 | // FaceApp 4 | // 5 | // Created by Arco on 2018/5/15. 6 | // Copyright © 2018 c. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SnapKit 11 | 12 | class MyCollectionViewCell: UICollectionViewCell { 13 | 14 | var imageView: UIImageView! 15 | var labelView: UILabel! 16 | override init(frame: CGRect) { 17 | super.init(frame: frame) 18 | 19 | // self.backgroundColor = UIColor.white 20 | 21 | let topView = UIImageView() 22 | let bottomView = UILabel() 23 | 24 | self.imageView = topView 25 | self.labelView = bottomView 26 | self.labelView?.textAlignment = .center 27 | 28 | self.contentView.addSubview(topView) 29 | self.contentView.addSubview(bottomView) 30 | 31 | topView.snp.makeConstraints { (make) -> Void in 32 | make.top.equalTo(self.contentView) 33 | make.centerX.equalTo(self.contentView) 34 | make.width.equalTo(50) 35 | make.height.equalTo(50) 36 | } 37 | 38 | bottomView.snp.makeConstraints { (make) -> Void in 39 | // make.top.equalTo(topView) 40 | make.bottom.equalTo(self.contentView) 41 | make.width.equalTo(self.contentView) 42 | make.height.equalTo(20) 43 | } 44 | 45 | 46 | } 47 | 48 | required init?(coder aDecoder: NSCoder) { 49 | super.init(coder: aDecoder) 50 | fatalError("init(coder:) has not been implemented") 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /FaceApp/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /FaceApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSAppTransportSecurity 6 | 7 | NSAllowsArbitraryLoads 8 | 9 | 10 | CFBundleDevelopmentRegion 11 | $(DEVELOPMENT_LANGUAGE) 12 | CFBundleExecutable 13 | $(EXECUTABLE_NAME) 14 | CFBundleIcons 15 | 16 | CFBundleIcons~ipad 17 | 18 | CFBundleIdentifier 19 | $(PRODUCT_BUNDLE_IDENTIFIER) 20 | CFBundleInfoDictionaryVersion 21 | 6.0 22 | CFBundleName 23 | $(PRODUCT_NAME) 24 | CFBundlePackageType 25 | APPL 26 | CFBundleShortVersionString 27 | 1.0 28 | CFBundleVersion 29 | 1 30 | LSRequiresIPhoneOS 31 | 32 | NSCameraUsageDescription 33 | Camera usage description 34 | NSPhotoLibraryUsageDescription 35 | Photo Library Usage Description 36 | NSPhotoLibraryAddUsageDescription 37 | NSPhotoLibraryAddUsageDescription 38 | UILaunchStoryboardName 39 | LaunchScreen 40 | UIRequiredDeviceCapabilities 41 | 42 | UISupportedInterfaceOrientations 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | UISupportedInterfaceOrientations~ipad 49 | 50 | UIInterfaceOrientationPortrait 51 | UIInterfaceOrientationPortraitUpsideDown 52 | UIInterfaceOrientationLandscapeLeft 53 | UIInterfaceOrientationLandscapeRight 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /FaceApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "60x60", 35 | "idiom" : "iphone", 36 | "filename" : "246x0w-2.jpg", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "idiom" : "iphone", 41 | "size" : "60x60", 42 | "scale" : "3x" 43 | }, 44 | { 45 | "idiom" : "ipad", 46 | "size" : "20x20", 47 | "scale" : "1x" 48 | }, 49 | { 50 | "idiom" : "ipad", 51 | "size" : "20x20", 52 | "scale" : "2x" 53 | }, 54 | { 55 | "idiom" : "ipad", 56 | "size" : "29x29", 57 | "scale" : "1x" 58 | }, 59 | { 60 | "idiom" : "ipad", 61 | "size" : "29x29", 62 | "scale" : "2x" 63 | }, 64 | { 65 | "idiom" : "ipad", 66 | "size" : "40x40", 67 | "scale" : "1x" 68 | }, 69 | { 70 | "idiom" : "ipad", 71 | "size" : "40x40", 72 | "scale" : "2x" 73 | }, 74 | { 75 | "idiom" : "ipad", 76 | "size" : "76x76", 77 | "scale" : "1x" 78 | }, 79 | { 80 | "idiom" : "ipad", 81 | "size" : "76x76", 82 | "scale" : "2x" 83 | }, 84 | { 85 | "idiom" : "ipad", 86 | "size" : "83.5x83.5", 87 | "scale" : "2x" 88 | }, 89 | { 90 | "idiom" : "ios-marketing", 91 | "size" : "1024x1024", 92 | "scale" : "1x" 93 | } 94 | ], 95 | "info" : { 96 | "version" : 1, 97 | "author" : "xcode" 98 | } 99 | } -------------------------------------------------------------------------------- /FaceApp/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // FaceApp 4 | // 5 | // Created by Arco on 2018/5/6. 6 | // Copyright © 2018 c. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | window = UIWindow(frame: UIScreen.main.bounds) 20 | self.window?.makeKeyAndVisible() 21 | // let mainViewController = MainViewController() 22 | // self.window?.rootViewController = mainViewController 23 | let nav = UINavigationController() 24 | let loginViewController = LoginViewController() 25 | nav.viewControllers = [loginViewController] 26 | // let horizontalViewController = HorizontalViewController() 27 | // nav.viewControllers = [horizontalViewController] 28 | self.window?.rootViewController = nav 29 | window?.makeKeyAndVisible() 30 | return true 31 | } 32 | 33 | func applicationWillResignActive(_ application: UIApplication) { 34 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 35 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 36 | } 37 | 38 | func applicationDidEnterBackground(_ application: UIApplication) { 39 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 40 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 41 | } 42 | 43 | func applicationWillEnterForeground(_ application: UIApplication) { 44 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 45 | } 46 | 47 | func applicationDidBecomeActive(_ application: UIApplication) { 48 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 49 | } 50 | 51 | func applicationWillTerminate(_ application: UIApplication) { 52 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 53 | } 54 | 55 | 56 | } 57 | 58 | -------------------------------------------------------------------------------- /FaceApp/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /FaceApp/HorizontalViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HorizontalViewController.swift 3 | // FaceApp 4 | // 5 | // Created by Arco on 2018/5/15. 6 | // Copyright © 2018 c. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class HorizontalViewController: UIViewController, UICollectionViewDataSource,UICollectionViewDelegate, UICollectionViewDelegateFlowLayout { 12 | 13 | var collectionView: UICollectionView? 14 | var bgColor: [UIColor] = [UIColor.green, UIColor.black, UIColor.yellow] 15 | var images: [UIImage] = [UIImage(named: "neutral")!, UIImage(named: "happy")!, UIImage(named: "surprise")!, 16 | UIImage(named: "sad")!, UIImage(named: "anger")!, UIImage(named: "fear")!, UIImage(named: "disgust")!] 17 | var captions: [String] = ["neutral", "happy", "surprise", "sad", "anger", "fear", "disgust"] 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | 21 | // Do any additional setup after loading the view. 22 | self.view.backgroundColor = UIColor.white 23 | 24 | let layout = UICollectionViewFlowLayout() 25 | layout.scrollDirection = .horizontal 26 | 27 | let bottomView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout) 28 | bottomView.backgroundColor = UIColor.clear 29 | self.collectionView = bottomView 30 | 31 | bottomView.collectionViewLayout = layout 32 | bottomView.dataSource = self 33 | bottomView.delegate = self 34 | 35 | bottomView.register(MyCollectionViewCell.self, forCellWithReuseIdentifier: "collectionViewCell") 36 | 37 | self.view.addSubview(bottomView) 38 | bottomView.snp.makeConstraints{ (make) -> Void in 39 | make.bottom.equalTo(self.view) 40 | make.left.equalTo(self.view) 41 | make.right.equalTo(self.view) 42 | make.height.equalTo(80) 43 | } 44 | 45 | } 46 | 47 | override func didReceiveMemoryWarning() { 48 | super.didReceiveMemoryWarning() 49 | // Dispose of any resources that can be recreated. 50 | } 51 | 52 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 53 | return 7 54 | } 55 | 56 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 57 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionViewCell", for: indexPath) as! MyCollectionViewCell 58 | // cell.backgroundColor = self.bgColor[indexPath.row] 59 | cell.imageView.image = self.images[indexPath.row] 60 | cell.labelView.text = self.captions[indexPath.row] 61 | return cell 62 | } 63 | 64 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 65 | // return CGSize(width: self.view.frame.size.width * 0.3, height: self.view.frame.size.width * 0.4) 66 | return CGSize(width: 80, height: 80) 67 | } 68 | 69 | /* 70 | // MARK: - Navigation 71 | 72 | // In a storyboard-based application, you will often want to do a little preparation before navigation 73 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 74 | // Get the new view controller using segue.destinationViewController. 75 | // Pass the selected object to the new view controller. 76 | } 77 | */ 78 | 79 | } 80 | -------------------------------------------------------------------------------- /FaceApp/TableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewController.swift 3 | // FaceApp 4 | // 5 | // Created by Arco on 2018/5/13. 6 | // Copyright © 2018 c. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class TableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate{ 12 | 13 | var myTableView: UITableView = UITableView() 14 | var itemsToLoad: [String] = ["One", "Two", "Three"] 15 | var delegate: Expression? 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | 20 | // Uncomment the following line to preserve selection between presentations 21 | // self.clearsSelectionOnViewWillAppear = false 22 | 23 | // Uncomment the following line to display an Edit button in the navigation bar for this view controller. 24 | // self.navigationItem.rightBarButtonItem = self.editButtonItem 25 | // self.navigationController?.setNavigationBarHidden(false, animated: true) 26 | myTableView.dataSource = self 27 | myTableView.delegate = self 28 | 29 | myTableView.register(UITableViewCell.self, forCellReuseIdentifier: "myCell") 30 | 31 | self.view.addSubview(myTableView) 32 | myTableView.snp.makeConstraints{ (make) -> Void in 33 | make.top.equalTo(self.view) 34 | make.left.equalTo(self.view) 35 | make.right.equalTo(self.view) 36 | make.bottom.equalTo(self.view) 37 | } 38 | 39 | self.navigationController!.navigationItem.backBarButtonItem?.title = "Done" 40 | } 41 | 42 | override func didReceiveMemoryWarning() { 43 | super.didReceiveMemoryWarning() 44 | // Dispose of any resources that can be recreated. 45 | } 46 | 47 | // MARK: - Table view data source 48 | 49 | func numberOfSections(in tableView: UITableView) -> Int { 50 | // #warning Incomplete implementation, return the number of sections 51 | return 1 52 | } 53 | 54 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 55 | // #warning Incomplete implementation, return the number of rows 56 | return self.itemsToLoad.count 57 | } 58 | 59 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 60 | let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) 61 | 62 | cell.textLabel?.text = self.itemsToLoad[indexPath.row] 63 | 64 | // Configure the cell... 65 | 66 | return cell 67 | } 68 | 69 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 70 | delegate?.changeExpression(expression: self.itemsToLoad[indexPath.row]) 71 | } 72 | 73 | /* 74 | // Override to support conditional editing of the table view. 75 | override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { 76 | // Return false if you do not want the specified item to be editable. 77 | return true 78 | } 79 | */ 80 | 81 | /* 82 | // Override to support editing the table view. 83 | override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { 84 | if editingStyle == .delete { 85 | // Delete the row from the data source 86 | tableView.deleteRows(at: [indexPath], with: .fade) 87 | } else if editingStyle == .insert { 88 | // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view 89 | } 90 | } 91 | */ 92 | 93 | /* 94 | // Override to support rearranging the table view. 95 | override func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to: IndexPath) { 96 | 97 | } 98 | */ 99 | 100 | /* 101 | // Override to support conditional rearranging of the table view. 102 | override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool { 103 | // Return false if you do not want the item to be re-orderable. 104 | return true 105 | } 106 | */ 107 | 108 | /* 109 | // MARK: - Navigation 110 | 111 | // In a storyboard-based application, you will often want to do a little preparation before navigation 112 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 113 | // Get the new view controller using segue.destinationViewController. 114 | // Pass the selected object to the new view controller. 115 | } 116 | */ 117 | 118 | } 119 | -------------------------------------------------------------------------------- /FaceApp/PreviewView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreviewView.swift 3 | // VisionDetection 4 | // 5 | // Created by Wei Chieh Tseng on 09/06/2017. 6 | // Copyright © 2017 Willjay. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Vision 11 | import AVFoundation 12 | 13 | class PreviewView: UIView { 14 | 15 | private var maskLayer = [CAShapeLayer]() 16 | 17 | // MARK: AV capture properties 18 | var videoPreviewLayer: AVCaptureVideoPreviewLayer { 19 | return layer as! AVCaptureVideoPreviewLayer 20 | } 21 | 22 | var session: AVCaptureSession? { 23 | get { 24 | return videoPreviewLayer.session 25 | } 26 | 27 | set { 28 | videoPreviewLayer.session = newValue 29 | } 30 | } 31 | 32 | override class var layerClass: AnyClass { 33 | return AVCaptureVideoPreviewLayer.self 34 | } 35 | 36 | // Create a new layer drawing the bounding box 37 | private func createLayer(in rect: CGRect) -> CAShapeLayer{ 38 | 39 | let mask = CAShapeLayer() 40 | mask.frame = rect 41 | mask.cornerRadius = 10 42 | mask.opacity = 0.75 43 | mask.borderColor = UIColor.yellow.cgColor 44 | mask.borderWidth = 2.0 45 | 46 | maskLayer.append(mask) 47 | layer.insertSublayer(mask, at: 1) 48 | 49 | return mask 50 | } 51 | 52 | func drawFaceboundingBox(face : VNFaceObservation) { 53 | 54 | let transform = CGAffineTransform(scaleX: 1, y: -1).translatedBy(x: 0, y: -frame.height) 55 | 56 | let translate = CGAffineTransform.identity.scaledBy(x: frame.width, y: frame.height) 57 | 58 | // The coordinates are normalized to the dimensions of the processed image, with the origin at the image's lower-left corner. 59 | let facebounds = face.boundingBox.applying(translate).applying(transform) 60 | 61 | _ = createLayer(in: facebounds) 62 | } 63 | 64 | func drawFaceWithLandmarks(face: VNFaceObservation) { 65 | 66 | let transform = CGAffineTransform(scaleX: 1, y: -1).translatedBy(x: 0, y: -frame.height) 67 | 68 | let translate = CGAffineTransform.identity.scaledBy(x: frame.width, y: frame.height) 69 | 70 | // The coordinates are normalized to the dimensions of the processed image, with the origin at the image's lower-left corner. 71 | let facebounds = face.boundingBox.applying(translate).applying(transform) 72 | 73 | // Draw the bounding rect 74 | let faceLayer = createLayer(in: facebounds) 75 | 76 | // Draw the landmarks 77 | drawLandmarks(on: faceLayer, faceLandmarkRegion: (face.landmarks?.nose)!, isClosed:false) 78 | drawLandmarks(on: faceLayer, faceLandmarkRegion: (face.landmarks?.noseCrest)!, isClosed:false) 79 | drawLandmarks(on: faceLayer, faceLandmarkRegion: (face.landmarks?.medianLine)!, isClosed:false) 80 | drawLandmarks(on: faceLayer, faceLandmarkRegion: (face.landmarks?.leftEye)!) 81 | drawLandmarks(on: faceLayer, faceLandmarkRegion: (face.landmarks?.leftPupil)!) 82 | drawLandmarks(on: faceLayer, faceLandmarkRegion: (face.landmarks?.leftEyebrow)!, isClosed:false) 83 | drawLandmarks(on: faceLayer, faceLandmarkRegion: (face.landmarks?.rightEye)!) 84 | drawLandmarks(on: faceLayer, faceLandmarkRegion: (face.landmarks?.rightPupil)!) 85 | drawLandmarks(on: faceLayer, faceLandmarkRegion: (face.landmarks?.rightEye)!) 86 | drawLandmarks(on: faceLayer, faceLandmarkRegion: (face.landmarks?.rightEyebrow)!, isClosed:false) 87 | drawLandmarks(on: faceLayer, faceLandmarkRegion: (face.landmarks?.innerLips)!) 88 | drawLandmarks(on: faceLayer, faceLandmarkRegion: (face.landmarks?.outerLips)!) 89 | drawLandmarks(on: faceLayer, faceLandmarkRegion: (face.landmarks?.faceContour)!, isClosed: false) 90 | } 91 | 92 | 93 | func drawLandmarks(on targetLayer: CALayer, faceLandmarkRegion: VNFaceLandmarkRegion2D, isClosed: Bool = true) { 94 | let rect: CGRect = targetLayer.frame 95 | var points: [CGPoint] = [] 96 | 97 | for i in 0.. CALayer { 115 | let linePath = UIBezierPath() 116 | linePath.move(to: landmarkPoints.first!) 117 | 118 | for point in landmarkPoints.dropFirst() { 119 | linePath.addLine(to: point) 120 | } 121 | 122 | if isClosed { 123 | linePath.addLine(to: landmarkPoints.first!) 124 | } 125 | 126 | let lineLayer = CAShapeLayer() 127 | lineLayer.path = linePath.cgPath 128 | lineLayer.fillColor = nil 129 | lineLayer.opacity = 1.0 130 | lineLayer.strokeColor = UIColor.green.cgColor 131 | lineLayer.lineWidth = 0.02 132 | 133 | return lineLayer 134 | } 135 | 136 | func removeMask() { 137 | for mask in maskLayer { 138 | mask.removeFromSuperlayer() 139 | } 140 | maskLayer.removeAll() 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /FaceApp/MainViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainViewController.swift 3 | // FaceApp 4 | // 5 | // Created by Arco on 2018/5/7. 6 | // Copyright © 2018 c. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation 11 | import SnapKit 12 | 13 | class MainViewController: UIViewController, UINavigationControllerDelegate, UIImagePickerControllerDelegate { 14 | 15 | var shutterBtn: UIButton? 16 | var faceImg: UIImage? 17 | var captureSession: AVCaptureSession? 18 | var videoPreviewLayer: AVCaptureVideoPreviewLayer? 19 | var capturePhotoOutput: AVCapturePhotoOutput? 20 | 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | // Do any additional setup after loading the view. 24 | 25 | self.navigationController?.setNavigationBarHidden(true, animated: false) 26 | let previewView = UIView() 27 | self.view.addSubview(previewView) 28 | 29 | let blurEffect = UIBlurEffect(style: .extraLight) 30 | let controlPanel = UIVisualEffectView(effect: blurEffect) 31 | self.view.addSubview(controlPanel) 32 | 33 | let btnImage = UIImage(named: "trigger") 34 | let triggerButton = UIButton() 35 | self.shutterBtn = triggerButton 36 | triggerButton.setImage(btnImage, for: .normal) 37 | triggerButton.contentMode = .scaleToFill 38 | 39 | controlPanel.addSubview(triggerButton) 40 | 41 | let albumImage = UIImage(named: "album") 42 | let albumBtn = UIButton() 43 | albumBtn.setImage(albumImage, for: .normal) 44 | albumBtn.contentMode = .scaleToFill 45 | 46 | controlPanel.addSubview(albumBtn) 47 | 48 | previewView.snp.makeConstraints{ (make) -> Void in 49 | make.top.equalTo(self.view) 50 | make.width.equalTo(self.view) 51 | make.height.equalTo(self.view) 52 | } 53 | 54 | controlPanel.snp.makeConstraints{ (make) -> Void in 55 | make.bottom.equalTo(self.view) 56 | make.width.equalTo(self.view) 57 | make.height.equalTo(self.view).multipliedBy(0.13) 58 | } 59 | 60 | triggerButton.snp.makeConstraints{ (make) -> Void in 61 | make.width.equalTo(controlPanel.snp.height).multipliedBy(0.9) 62 | make.height.equalTo(triggerButton.snp.width) 63 | make.center.equalTo(controlPanel) 64 | } 65 | 66 | triggerButton.addTarget(self, action: #selector(shutter), for: .touchUpInside) 67 | 68 | albumBtn.snp.makeConstraints { (make) -> Void in 69 | make.centerY.equalTo(triggerButton) 70 | make.left.equalTo(controlPanel).offset(10) 71 | make.width.equalTo(controlPanel.snp.height).multipliedBy(0.9) 72 | make.height.equalTo(albumBtn.snp.width) 73 | } 74 | 75 | albumBtn.addTarget(self, action: #selector(checkPhotos), for: .touchUpInside) 76 | 77 | let captureDevice = AVCaptureDevice.default(for: AVMediaType.video) 78 | do { 79 | let input = try AVCaptureDeviceInput(device: captureDevice!) 80 | captureSession = AVCaptureSession() 81 | captureSession?.addInput(input) 82 | videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession!) 83 | videoPreviewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill 84 | videoPreviewLayer?.frame = view.layer.bounds 85 | previewView.layer.addSublayer(videoPreviewLayer!) 86 | captureSession?.startRunning() 87 | 88 | // Get an instance of ACCapturePhotoOutput class 89 | capturePhotoOutput = AVCapturePhotoOutput() 90 | capturePhotoOutput?.isHighResolutionCaptureEnabled = true 91 | // Set the output on the capture session 92 | captureSession?.addOutput(capturePhotoOutput!) 93 | } catch { 94 | print(error) 95 | } 96 | 97 | 98 | } 99 | 100 | override func viewDidAppear(_ animated: Bool) { 101 | self.shutterBtn?.isEnabled = true 102 | } 103 | @objc func shutter(){ 104 | // Make sure capturePhotoOutput is valid 105 | self.shutterBtn?.isEnabled = false 106 | guard let capturePhotoOutput = self.capturePhotoOutput else { return } 107 | // Get an instance of AVCapturePhotoSettings class 108 | let photoSettings = AVCapturePhotoSettings() 109 | // Set photo settings for our need 110 | photoSettings.isAutoStillImageStabilizationEnabled = true 111 | photoSettings.isHighResolutionPhotoEnabled = true 112 | photoSettings.flashMode = .auto 113 | // Call capturePhoto method by passing our photo settings and a 114 | // delegate implementing AVCapturePhotoCaptureDelegate 115 | capturePhotoOutput.capturePhoto(with: photoSettings, delegate: self) 116 | 117 | } 118 | 119 | @objc func checkPhotos(){ 120 | print("albumbtn clicked") 121 | let imagePickerController = UIImagePickerController() 122 | imagePickerController.delegate = self 123 | imagePickerController.sourceType = UIImagePickerControllerSourceType.savedPhotosAlbum 124 | imagePickerController.allowsEditing = true 125 | self.present(imagePickerController, animated: true, completion: nil) 126 | 127 | } 128 | 129 | func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) { 130 | if let pickedImage = info[UIImagePickerControllerOriginalImage] as? UIImage { 131 | let processViewController = ProcessViewController() 132 | processViewController.faceImg = pickedImage 133 | self.navigationController?.pushViewController(processViewController, animated: true) 134 | } 135 | 136 | dismiss(animated: true, completion: nil) 137 | 138 | } 139 | 140 | override func didReceiveMemoryWarning() { 141 | super.didReceiveMemoryWarning() 142 | // Dispose of any resources that can be recreated. 143 | } 144 | 145 | /* 146 | // MARK: - Navigation 147 | 148 | // In a storyboard-based application, you will often want to do a little preparation before navigation 149 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 150 | // Get the new view controller using segue.destinationViewController. 151 | // Pass the selected object to the new view controller. 152 | } 153 | */ 154 | 155 | } 156 | 157 | extension MainViewController: AVCapturePhotoCaptureDelegate { 158 | func photoOutput(_ captureOutput: AVCapturePhotoOutput, 159 | didFinishProcessingPhoto photoSampleBuffer: CMSampleBuffer?, 160 | previewPhoto previewPhotoSampleBuffer: CMSampleBuffer?, 161 | resolvedSettings: AVCaptureResolvedPhotoSettings, 162 | bracketSettings: AVCaptureBracketedStillImageSettings?, 163 | error: Error?) { 164 | // get captured image 165 | // Make sure we get some photo sample buffer 166 | guard error == nil, let photoSampleBuffer = photoSampleBuffer else { 167 | print("Error capturing photo: \(String(describing: error))") 168 | return 169 | } 170 | // Convert photo same buffer to a jpeg image data by using // AVCapturePhotoOutput 171 | guard let imageData = 172 | AVCapturePhotoOutput.jpegPhotoDataRepresentation(forJPEGSampleBuffer: photoSampleBuffer, previewPhotoSampleBuffer: previewPhotoSampleBuffer) else { 173 | return 174 | } 175 | // Initialise a UIImage with our image data 176 | let capturedImage = UIImage.init(data: imageData , scale: 1.0) 177 | self.faceImg = capturedImage 178 | let processViewController = ProcessViewController() 179 | processViewController.faceImg = capturedImage 180 | self.navigationController?.pushViewController(processViewController, animated: true) 181 | // if let image = capturedImage { 182 | // // Save our captured image to photos album 183 | // UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil) 184 | // } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /FaceApp/LoginViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // FaceApp 4 | // 5 | // Created by Arco on 2018/5/6. 6 | // Copyright © 2018 c. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SnapKit 11 | import Alamofire 12 | import Toast_Swift 13 | 14 | let brightBlue = UIColor(red: 0, green: 118/255, blue: 1, alpha: 0.5) 15 | let brightGray = UIColor(red: 155/255, green: 155/255, blue: 155/255, alpha: 1) 16 | 17 | class LoginViewController: UIViewController { 18 | 19 | weak var logoView: UIImageView! 20 | weak var usernameTextView: UITextField! 21 | weak var passwordTextView: UITextField! 22 | weak var captchaTextView: UITextField! 23 | weak var captchaImageView: UIImageView! 24 | weak var maskView: UIVisualEffectView! 25 | var base_name: String! 26 | // let host = "http://218.193.183.249:8888" 27 | // let host = "http://192.168.3.191:8000" 28 | // let host = "http://192.168.1.105:8000" 29 | let host = "http://192.168.3.21:8000" 30 | var username: String? { 31 | get { 32 | return usernameTextView.text?.trim() 33 | } 34 | } 35 | var password: String? { 36 | get { 37 | return passwordTextView.text?.trim() 38 | } 39 | } 40 | 41 | var captcha: String? { 42 | get { 43 | return captchaTextView.text?.trim() 44 | } 45 | } 46 | 47 | override func viewDidLoad() { 48 | super.viewDidLoad() 49 | // Do any additional setup after loading the view, typically from a nib. 50 | NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil) 51 | NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil) 52 | 53 | let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard (_:))) 54 | self.view.addGestureRecognizer(tapGesture) 55 | 56 | let loadingBlurEffect = UIBlurEffect(style: .extraLight) 57 | let maskView = UIVisualEffectView(effect: loadingBlurEffect) 58 | self.maskView = maskView 59 | 60 | self.view.backgroundColor = UIColor.white 61 | self.navigationController?.setNavigationBarHidden(true, animated: false) 62 | let mainImageView = UIImageView(image: UIImage(named: "sjtuicon")) 63 | self.logoView = mainImageView 64 | 65 | let usernameTextField = UITextField() 66 | usernameTextField.borderStyle = .roundedRect 67 | usernameTextField.layer.borderWidth = 1 68 | usernameTextField.layer.cornerRadius = 5 69 | usernameTextField.layer.borderColor = brightGray.cgColor 70 | usernameTextField.attributedPlaceholder = NSAttributedString(string: "用户名") 71 | 72 | let passwordTextField = UITextField() 73 | passwordTextField.isSecureTextEntry = true 74 | passwordTextField.borderStyle = .roundedRect 75 | passwordTextField.layer.borderWidth = 1 76 | passwordTextField.layer.cornerRadius = 5 77 | passwordTextField.layer.borderColor = brightGray.cgColor 78 | passwordTextField.attributedPlaceholder = NSAttributedString(string: "密码") 79 | 80 | let submitButton = UIButton() 81 | submitButton.setAttributedTitle(NSAttributedString(string: "提交"), for: .normal) 82 | submitButton.layer.borderWidth = 1 83 | submitButton.layer.cornerRadius = 5 84 | submitButton.layer.borderColor = brightGray.cgColor 85 | 86 | self.usernameTextView = usernameTextField 87 | self.passwordTextView = passwordTextField 88 | 89 | self.view.addSubview(mainImageView) 90 | self.view.addSubview(usernameTextField) 91 | self.view.addSubview(passwordTextField) 92 | self.view.addSubview(submitButton) 93 | 94 | let captchaView = UIView() 95 | self.view.addSubview(captchaView) 96 | 97 | let captchaTextField = UITextField() 98 | captchaTextField.borderStyle = .roundedRect 99 | captchaTextField.layer.borderWidth = 1 100 | captchaTextField.layer.cornerRadius = 5 101 | captchaTextField.layer.borderColor = brightGray.cgColor 102 | captchaTextField.attributedPlaceholder = NSAttributedString(string: "验证码") 103 | 104 | let captchaImageView = UIImageView(image: UIImage(named: "captcha")) 105 | 106 | self.captchaTextView = captchaTextField 107 | self.captchaImageView = captchaImageView 108 | 109 | captchaView.addSubview(captchaTextField) 110 | captchaView.addSubview(captchaImageView) 111 | 112 | mainImageView.snp.makeConstraints{ (make) -> Void in 113 | make.centerX.equalTo(self.view) 114 | make.top.equalTo(self.view).offset(72) 115 | make.width.equalTo(self.view).multipliedBy(0.5) 116 | make.height.equalTo(mainImageView.snp.width) 117 | } 118 | 119 | usernameTextField.snp.makeConstraints{ (make) -> Void in 120 | make.centerX.equalTo(self.view) 121 | make.top.equalTo(mainImageView.snp.bottom).offset(51) 122 | make.width.equalTo(self.view).multipliedBy(0.8) 123 | make.height.equalTo(44) 124 | } 125 | 126 | passwordTextField.snp.makeConstraints{ (make) -> Void in 127 | make.centerX.equalTo(self.view) 128 | make.top.equalTo(usernameTextField.snp.bottom).offset(20) 129 | make.width.equalTo(self.view).multipliedBy(0.8) 130 | make.height.equalTo(44) 131 | } 132 | 133 | captchaView.snp.makeConstraints { (make) -> Void in 134 | make.centerX.equalTo(self.view) 135 | make.width.equalTo(self.view).multipliedBy(0.8) 136 | make.top.equalTo(passwordTextField.snp.bottom).offset(20) 137 | make.height.equalTo(44) 138 | } 139 | 140 | captchaTextField.snp.makeConstraints { (make) -> Void in 141 | make.top.equalTo(captchaView) 142 | make.bottom.equalTo(captchaView) 143 | make.left.equalTo(captchaView) 144 | make.right.equalTo(captchaImageView.snp.left).offset(-10) 145 | } 146 | 147 | captchaImageView.snp.makeConstraints { (make) -> Void in 148 | make.top.equalTo(captchaView) 149 | make.bottom.equalTo(captchaView) 150 | // make.left.equalTo(captchaTextField.snp.right) 151 | make.right.equalTo(captchaView) 152 | make.width.lessThanOrEqualTo(captchaView).multipliedBy(0.5) 153 | } 154 | 155 | submitButton.snp.makeConstraints{ (make) -> Void in 156 | make.centerX.equalTo(self.view) 157 | make.top.equalTo(captchaView.snp.bottom).offset(31) 158 | make.width.equalTo(120) 159 | make.height.equalTo(44) 160 | } 161 | 162 | submitButton.addTarget(self, action: #selector(loginToSjtu), for: .touchUpInside) 163 | 164 | self.view.addSubview(self.maskView) 165 | self.maskView.snp.makeConstraints { (make) in 166 | make.width.equalTo(self.view) 167 | make.height.equalTo(self.view) 168 | } 169 | 170 | self.maskView.isHidden = true 171 | } 172 | 173 | override func viewDidAppear(_ animated: Bool) { 174 | getCaptcha() 175 | } 176 | 177 | @objc func dismissKeyboard (_ sender: UITapGestureRecognizer) { 178 | self.view.endEditing(true) 179 | } 180 | 181 | @objc func keyboardWillShow(notification: NSNotification) { 182 | if self.view.frame.origin.y == 0{ 183 | self.view.frame.origin.y -= self.logoView.frame.height + 72 184 | } 185 | } 186 | 187 | @objc func keyboardWillHide(notification: NSNotification) { 188 | if self.view.frame.origin.y != 0{ 189 | self.view.frame.origin.y += self.logoView.frame.height + 72 190 | } 191 | } 192 | 193 | @objc func loginToSjtu(sender: UIButton){ 194 | login() 195 | } 196 | 197 | func getCaptcha(){ 198 | Alamofire.request("\(self.host)/demo/captcha").responseString { response in 199 | if let base_name = response.result.value { 200 | self.base_name = base_name 201 | self.getCaptchaFile(base_name) 202 | } 203 | } 204 | } 205 | 206 | func getCaptchaFile(_ base_name: String){ 207 | Alamofire.request("\(self.host)/demo/captcha_file?base_name=\(base_name)").responseData { response in 208 | if let data = response.result.value { 209 | let captcha = UIImage(data: data) 210 | self.captchaImageView.image = captcha 211 | } 212 | } 213 | } 214 | 215 | func login(){ 216 | if self.username == "admin" { 217 | let viewController = ViewController() 218 | self.navigationController?.pushViewController(viewController, animated: true) 219 | } 220 | guard self.username != "" else{ 221 | self.view.makeToast("请输入您的jAccount帐号") 222 | print("miss username") 223 | return 224 | } 225 | guard self.password != "" else{ 226 | self.view.makeToast("请输入您的密码") 227 | print("miss password") 228 | return 229 | } 230 | guard self.captcha != "" else{ 231 | self.view.makeToast("请输入验证码") 232 | print("miss captcha") 233 | return 234 | } 235 | guard self.base_name != "" else{ 236 | print("miss base_name") 237 | return 238 | } 239 | let parameters: Parameters = [ 240 | "username": self.username!, 241 | "password": self.password!, 242 | "base_name": self.base_name!, 243 | "captcha": self.captcha! 244 | ] 245 | 246 | UIView.transition(with: self.maskView, duration: 1, options: .transitionCrossDissolve, animations: { self.maskView.isHidden = false }, completion: nil) 247 | Alamofire.request("\(self.host)/demo/login", method: .post, parameters: parameters).responseString { response in 248 | if let status = response.result.value{ 249 | if status == "success"{ 250 | print("welcome, \(self.username!)") 251 | let viewController = ViewController() 252 | self.navigationController?.pushViewController(viewController, animated: true) 253 | } else { 254 | self.view.makeToast("请正确填写你的用户名、密码和验证码,注意:密码是区分大小写的") 255 | print("wrong username or password") 256 | self.getCaptcha() 257 | } 258 | UIView.transition(with: self.maskView, duration: 1, options: .transitionCrossDissolve, animations: { self.maskView.isHidden = true }, completion: nil) 259 | } 260 | } 261 | } 262 | 263 | func uploadImg(img: UIImage){ 264 | 265 | let orgiFace = UIImagePNGRepresentation(img)! 266 | Alamofire.upload(orgiFace, to: "http://127.0.0.1:8000/demo/upload/").responseData { response in 267 | if let data = response.result.value { 268 | let newFace = UIImage(data: data) 269 | self.logoView.image = newFace 270 | } 271 | } 272 | } 273 | 274 | override func didReceiveMemoryWarning() { 275 | super.didReceiveMemoryWarning() 276 | // Dispose of any resources that can be recreated. 277 | } 278 | 279 | 280 | } 281 | 282 | extension String 283 | { 284 | func trim() -> String 285 | { 286 | return self.trimmingCharacters(in: NSCharacterSet.whitespaces) 287 | } 288 | } 289 | 290 | -------------------------------------------------------------------------------- /FaceApp/ProcessViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProcessViewController.swift 3 | // FaceApp 4 | // 5 | // Created by Arco on 2018/5/13. 6 | // Copyright © 2018 c. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Alamofire 11 | import SwiftyJSON 12 | import Toast_Swift 13 | 14 | protocol Expression 15 | { 16 | func changeExpression(expression: String) 17 | } 18 | 19 | class ProcessViewController: UIViewController, UIScrollViewDelegate, UICollectionViewDataSource,UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, Expression { 20 | 21 | let api_key = "NeSGlSk3KBueWp-K6Cr18I_-MiuZ4l2u" 22 | let api_secret = "j22uKoQLiIw-5sazuu0lzCkYdNQEn_OQ" 23 | 24 | var scaleRect: CGRect? 25 | var faceImg: UIImage! 26 | var cropFace: UIImage! 27 | var needHide: Bool! 28 | var expression: String? 29 | weak var faceView: UIImageView! 30 | weak var scrollView: UIScrollView! 31 | var collectionView: UICollectionView? 32 | var expressionImages: [String:UIImage] = [:] 33 | var confidences: [String: String] = [:] 34 | var images: [UIImage] = [UIImage(named: "neutral")!, UIImage(named: "happy")!, UIImage(named: "surprise")!, 35 | UIImage(named: "sad")!, UIImage(named: "anger")!] 36 | var captions: [String] = ["neutral", "happy", "surprise", "sad", "anger"] 37 | var maskView: UIVisualEffectView! 38 | var loadingFlag = false 39 | // let host = "http://218.193.183.249:8888" 40 | // let host = "http://192.168.3.191:8000" 41 | // let host = "http://192.168.1.105:8000" 42 | let host = "http://192.168.3.21:8000" 43 | 44 | override func viewDidLoad() { 45 | super.viewDidLoad() 46 | 47 | // Do any additional setup after loading the view. 48 | self.needHide = true 49 | 50 | let loadingBlurEffect = UIBlurEffect(style: .extraLight) 51 | self.maskView = UIVisualEffectView(effect: loadingBlurEffect) 52 | 53 | self.navigationController?.setNavigationBarHidden(false, animated: false) 54 | self.view.backgroundColor = UIColor.white 55 | 56 | if let scale = self.scaleRect { 57 | self.faceImg = self.faceImg.crop(rect: scale) 58 | self.faceImg = self.faceImg.imageWithImage(scaledToSize: CGSize(width: 128, height: 128)) 59 | self.faceImg = self.faceImg.fixOrientation() 60 | } 61 | else { 62 | self.faceImg = self.faceImg.imageWithImage(scaledToSize: CGSize(width: 128, height: 128)) 63 | self.cropFace = self.faceImg 64 | } 65 | self.expressionImages["neutral"] = self.faceImg 66 | 67 | let imageView = UIImageView(image: faceImg) 68 | imageView.contentMode = .scaleAspectFit 69 | self.faceView = imageView 70 | 71 | let mainView = UIScrollView() 72 | mainView.delegate = self 73 | 74 | self.scrollView = mainView 75 | mainView.minimumZoomScale = 0.5; 76 | mainView.maximumZoomScale = 4.0; 77 | mainView.showsHorizontalScrollIndicator = false 78 | mainView.showsVerticalScrollIndicator = false 79 | 80 | mainView.addSubview(imageView) 81 | self.view.addSubview(mainView) 82 | 83 | let layout = UICollectionViewFlowLayout() 84 | layout.scrollDirection = .horizontal 85 | 86 | let selectionPanel = UICollectionView(frame: self.view.frame, collectionViewLayout: layout) 87 | selectionPanel.showsVerticalScrollIndicator = false 88 | selectionPanel.showsVerticalScrollIndicator = false 89 | 90 | self.collectionView = selectionPanel 91 | 92 | selectionPanel.dataSource = self 93 | selectionPanel.delegate = self 94 | 95 | selectionPanel.register(MyCollectionViewCell.self, forCellWithReuseIdentifier: "collectionViewCell") 96 | 97 | self.view.addSubview(selectionPanel) 98 | 99 | mainView.snp.makeConstraints { (make) -> Void in 100 | make.width.equalTo(self.view) 101 | make.height.equalTo(self.view) 102 | make.center.equalTo(self.view) 103 | } 104 | 105 | let imageAspect = self.faceImg.size.height / self.faceImg.size.width 106 | 107 | imageView.snp.makeConstraints { (make) -> Void in 108 | make.width.equalToSuperview().multipliedBy(1.0) 109 | make.height.equalTo(imageView.snp.width).multipliedBy(imageAspect) 110 | } 111 | 112 | selectionPanel.snp.makeConstraints{ (make) -> Void in 113 | make.bottom.equalTo(self.view) 114 | make.left.equalTo(self.view) 115 | make.right.equalTo(self.view) 116 | make.height.equalTo(100) 117 | } 118 | 119 | let selectBtn = UIBarButtonItem(title: "Save", style: .plain, target: self, action: #selector(saveAction)) 120 | // let selectBtn = UIBarButtonItem(image: UIImage(named: "more"), style: .plain, target: self, action: #selector(moreAction)) 121 | 122 | self.navigationItem.rightBarButtonItem = selectBtn 123 | 124 | selectionPanel.backgroundColor = UIColor.clear 125 | 126 | let blurEffect = UIBlurEffect(style: .extraLight) 127 | let blurEffectView = UIVisualEffectView(effect: blurEffect) 128 | blurEffectView.frame = selectionPanel.frame 129 | selectionPanel.backgroundView = blurEffectView 130 | 131 | mainView.isScrollEnabled = true 132 | 133 | self.view.addSubview(self.maskView) 134 | maskView.snp.makeConstraints { (make) in 135 | make.width.equalTo(self.view) 136 | make.height.equalTo(self.view) 137 | } 138 | 139 | self.maskView.isHidden = true 140 | 141 | } 142 | 143 | func scrollViewDidZoom(_ scrollView: UIScrollView) { 144 | let imageViewSize = self.faceView.frame.size 145 | let scrollViewSize = scrollView.bounds.size 146 | let verticalInset = imageViewSize.height < scrollViewSize.height ? (scrollViewSize.height - imageViewSize.height) / 2 : 5 147 | let horizontalInset = imageViewSize.width < scrollViewSize.width ? (scrollViewSize.width - imageViewSize.width) / 2 : 5 148 | scrollView.contentInset = UIEdgeInsets(top: verticalInset, left: horizontalInset, bottom: verticalInset, right: horizontalInset) 149 | } 150 | 151 | func viewForZooming(in scrollView: UIScrollView) -> UIView? { 152 | return self.faceView 153 | } 154 | 155 | override func didReceiveMemoryWarning() { 156 | super.didReceiveMemoryWarning() 157 | // Dispose of any resources that can be recreated. 158 | } 159 | 160 | func compareFace(faceOrg: UIImage, faceChanged: UIImage, expression: String) { 161 | let imageDataOrg = UIImagePNGRepresentation(faceOrg)! 162 | let encodeStringOrg = imageDataOrg.base64EncodedString(options: Data.Base64EncodingOptions.lineLength64Characters) 163 | let imageDataChanged = UIImagePNGRepresentation(faceChanged)! 164 | let encodeStringChanged = imageDataChanged.base64EncodedString(options: Data.Base64EncodingOptions.lineLength64Characters) 165 | let parameters: Parameters = [ 166 | "api_key": api_key, 167 | "api_secret": api_secret, 168 | "image_base64_1": encodeStringOrg, 169 | "image_base64_2": encodeStringChanged 170 | ] 171 | Alamofire.request("https://api-cn.faceplusplus.com/facepp/v3/compare", method: .post, parameters: parameters) 172 | // .responseString { response in 173 | // print(response.result.value) 174 | // } 175 | .responseJSON { response in 176 | if response.data != nil { 177 | do { 178 | let json = try JSON(data: response.data!) 179 | let rawConfidence = json["confidence"].rawString() 180 | if let confidence = rawConfidence{ 181 | print(confidence) 182 | // self.confidences[expression] = confidence 183 | // self.view.makeToast("confidence: \(confidence)") 184 | // self.navigationItem.title = confidence 185 | } 186 | } 187 | catch { 188 | print("json praser error") 189 | 190 | } 191 | } 192 | } 193 | } 194 | 195 | @objc func saveAction(){ 196 | if let image = self.faceView.image { 197 | // Save our captured image to photos album 198 | UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil) 199 | print("Saved") 200 | // self.view.makeToast("Saved") 201 | } 202 | } 203 | 204 | override func viewWillDisappear(_ animated: Bool) { 205 | if self.needHide { 206 | self.navigationController?.setNavigationBarHidden(true, animated: true) 207 | } 208 | } 209 | 210 | override func viewWillAppear(_ animated: Bool) { 211 | print("\(expression ?? "no expression") is selected") 212 | } 213 | 214 | func changeExpression(expression: String) { 215 | self.expression = expression 216 | } 217 | 218 | func cropImg(img: UIImage){ 219 | let orgiFace = UIImagePNGRepresentation(img)! 220 | Alamofire.upload(orgiFace, to: self.host + "/demo/crop").responseData 221 | { response in 222 | if let data = response.result.value { 223 | let newFace = UIImage(data: data) 224 | self.cropFace = newFace 225 | } 226 | } 227 | } 228 | 229 | func uploadImg(img: UIImage, expression: String){ 230 | self.navigationItem.title = expression 231 | self.loadingFlag = true 232 | UIView.transition(with: self.maskView, duration: 1, options: .transitionCrossDissolve, animations: { self.maskView.isHidden = false }, completion: nil) 233 | // self.maskView.isHidden = false 234 | if self.cropFace == nil { 235 | let oldFace = UIImagePNGRepresentation(img)! 236 | Alamofire.upload(oldFace, to: self.host + "/demo/crop_ios").responseData 237 | { response in 238 | if let data = response.result.value { 239 | let newFace = UIImage(data: data) 240 | self.cropFace = newFace 241 | let orgiFace = UIImagePNGRepresentation(self.cropFace)! 242 | Alamofire.upload(orgiFace, to: self.host + "/demo/\(expression)").responseData { response in 243 | if let data = response.result.value { 244 | let newFace = UIImage(data: data) 245 | self.expressionImages[expression] = newFace 246 | self.faceView!.image = newFace 247 | self.loadingFlag = false 248 | // self.maskView.isHidden = true 249 | self.compareFace(faceOrg: self.faceImg, faceChanged: newFace!, expression: expression) 250 | UIView.transition(with: self.maskView, duration: 1, options: .transitionCrossDissolve, animations: { self.maskView.isHidden = true }, completion: nil) 251 | } 252 | } 253 | 254 | } 255 | } 256 | } 257 | else { 258 | let orgiFace = UIImagePNGRepresentation(self.cropFace)! 259 | Alamofire.upload(orgiFace, to: self.host + "/demo/\(expression)").responseData { response in 260 | if let data = response.result.value { 261 | let newFace = UIImage(data: data) 262 | self.expressionImages[expression] = newFace 263 | self.faceView!.image = newFace 264 | self.loadingFlag = false 265 | // self.maskView.isHidden = true 266 | self.compareFace(faceOrg: self.faceImg, faceChanged: newFace!, expression: expression) 267 | UIView.transition(with: self.maskView, duration: 1, options: .transitionCrossDissolve, animations: { self.maskView.isHidden = true }, completion: nil) 268 | } 269 | } 270 | } 271 | 272 | } 273 | 274 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 275 | return self.captions.count 276 | } 277 | 278 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 279 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionViewCell", for: indexPath) as! MyCollectionViewCell 280 | // cell.backgroundColor = self.bgColor[indexPath.row] 281 | cell.imageView.image = self.images[indexPath.row] 282 | cell.labelView.text = self.captions[indexPath.row] 283 | return cell 284 | } 285 | 286 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 287 | // return CGSize(width: self.view.frame.size.width * 0.3, height: self.view.frame.size.width * 0.4) 288 | return CGSize(width: 80, height: 75) 289 | } 290 | 291 | func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 292 | if self.loadingFlag { 293 | return 294 | } 295 | let expression = self.captions[indexPath.row] 296 | if let newFaceImg = self.expressionImages[expression] { 297 | if self.navigationItem.title == expression{ 298 | uploadImg(img: self.faceImg, expression: expression) 299 | } 300 | self.faceView.image = newFaceImg 301 | self.navigationItem.title = expression 302 | // self.navigationItem.title = self.confidences[expression] 303 | } else { 304 | uploadImg(img: self.faceImg, expression: expression) 305 | } 306 | } 307 | 308 | /* 309 | // MARK: - Navigation 310 | 311 | // In a storyboard-based application, you will often want to do a little preparation before navigation 312 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 313 | // Get the new view controller using segue.destinationViewController. 314 | // Pass the selected object to the new view controller. 315 | } 316 | */ 317 | 318 | } 319 | 320 | extension UIImage { 321 | func fixOrientation() -> UIImage { 322 | if self.imageOrientation == UIImageOrientation.up { 323 | return self 324 | } 325 | UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale) 326 | self.draw(in: CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height)) 327 | if let normalizedImage: UIImage = UIGraphicsGetImageFromCurrentImageContext() { 328 | UIGraphicsEndImageContext() 329 | return normalizedImage 330 | } else { 331 | return self 332 | } 333 | } 334 | 335 | func imageWithImage(scaledToSize newSize:CGSize) -> UIImage{ 336 | UIGraphicsBeginImageContextWithOptions(newSize, false, 0.0); 337 | self.draw(in: CGRect(origin: CGPoint.zero, size: CGSize(width: newSize.width, height: newSize.height))) 338 | let newImage:UIImage = UIGraphicsGetImageFromCurrentImageContext()! 339 | UIGraphicsEndImageContext() 340 | return newImage 341 | } 342 | } 343 | -------------------------------------------------------------------------------- /FaceApp/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // VisionDetection 4 | // 5 | // Created by Wei Chieh Tseng on 09/06/2017. 6 | // Copyright © 2017 Willjay. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation 11 | import Vision 12 | import SnapKit 13 | 14 | class ViewController: UIViewController { 15 | 16 | // VNRequest: Either Retangles or Landmarks 17 | var faceDetectionRequest: VNRequest! 18 | var previewView: PreviewView! 19 | 20 | var shutterBtn: UIButton? 21 | var capturePhotoOutput: AVCapturePhotoOutput? 22 | 23 | var detectedFace = false 24 | var firstFace: VNFaceObservation? 25 | 26 | override func viewDidLoad() { 27 | super.viewDidLoad() 28 | 29 | self.navigationController?.setNavigationBarHidden(true, animated: false) 30 | 31 | let cameraView = PreviewView() 32 | previewView = cameraView 33 | self.view.addSubview(cameraView) 34 | 35 | cameraView.snp.makeConstraints { (make) in 36 | make.width.equalToSuperview() 37 | make.height.equalToSuperview() 38 | } 39 | 40 | let blurEffect = UIBlurEffect(style: .extraLight) 41 | let controlPanel = UIVisualEffectView(effect: blurEffect) 42 | // let controlPanel = UIView() 43 | self.view.addSubview(controlPanel) 44 | 45 | controlPanel.snp.makeConstraints{ (make) -> Void in 46 | make.bottom.equalTo(self.view) 47 | make.width.equalTo(self.view) 48 | make.height.equalTo(self.view).multipliedBy(0.13) 49 | } 50 | 51 | let btnImage = UIImage(named: "trigger") 52 | let triggerButton = UIButton() 53 | self.shutterBtn = triggerButton 54 | triggerButton.setImage(btnImage, for: .normal) 55 | triggerButton.contentMode = .scaleToFill 56 | 57 | triggerButton.addTarget(self, action: #selector(shutter), for: .touchUpInside) 58 | controlPanel.contentView.addSubview(triggerButton) 59 | 60 | triggerButton.snp.makeConstraints{ (make) -> Void in 61 | make.width.equalTo(controlPanel.snp.height).multipliedBy(0.9) 62 | make.height.equalTo(triggerButton.snp.width) 63 | make.center.equalTo(controlPanel) 64 | } 65 | 66 | let albumImage = UIImage(named: "album") 67 | let albumBtn = UIButton() 68 | albumBtn.setImage(albumImage, for: .normal) 69 | albumBtn.contentMode = .scaleToFill 70 | 71 | albumBtn.addTarget(self, action: #selector(checkPhotos), for: .touchUpInside) 72 | controlPanel.contentView.addSubview(albumBtn) 73 | 74 | albumBtn.snp.makeConstraints { (make) -> Void in 75 | make.centerY.equalTo(triggerButton) 76 | make.left.equalTo(controlPanel).offset(10) 77 | make.width.equalTo(controlPanel.snp.height).multipliedBy(0.9) 78 | make.height.equalTo(albumBtn.snp.width) 79 | } 80 | 81 | // Get an instance of ACCapturePhotoOutput class 82 | capturePhotoOutput = AVCapturePhotoOutput() 83 | capturePhotoOutput?.isHighResolutionCaptureEnabled = true 84 | 85 | // Set the output on the capture session 86 | session.addOutput(capturePhotoOutput!) 87 | 88 | // Set up the video preview view. 89 | previewView.session = session 90 | 91 | // Set up Vision Request 92 | // faceDetectionRequest = VNDetectFaceRectanglesRequest(completionHandler: self.handleFaces) // Default 93 | faceDetectionRequest = VNDetectFaceLandmarksRequest(completionHandler: self.handleFaceLandmarks) // Default 94 | setupVision() 95 | 96 | /* 97 | Check video authorization status. Video access is required and audio 98 | access is optional. If audio access is denied, audio is not recorded 99 | during movie recording. 100 | */ 101 | switch AVCaptureDevice.authorizationStatus(for: AVMediaType.video){ 102 | case .authorized: 103 | // The user has previously granted access to the camera. 104 | break 105 | 106 | case .notDetermined: 107 | /* 108 | The user has not yet been presented with the option to grant 109 | video access. We suspend the session queue to delay session 110 | setup until the access request has completed. 111 | */ 112 | sessionQueue.suspend() 113 | AVCaptureDevice.requestAccess(for: AVMediaType.video, completionHandler: { [unowned self] granted in 114 | if !granted { 115 | self.setupResult = .notAuthorized 116 | } 117 | self.sessionQueue.resume() 118 | }) 119 | 120 | 121 | default: 122 | // The user has previously denied access. 123 | setupResult = .notAuthorized 124 | } 125 | 126 | /* 127 | Setup the capture session. 128 | In general it is not safe to mutate an AVCaptureSession or any of its 129 | inputs, outputs, or connections from multiple threads at the same time. 130 | 131 | Why not do all of this on the main queue? 132 | Because AVCaptureSession.startRunning() is a blocking call which can 133 | take a long time. We dispatch session setup to the sessionQueue so 134 | that the main queue isn't blocked, which keeps the UI responsive. 135 | */ 136 | 137 | sessionQueue.async { [unowned self] in 138 | self.configureSession() 139 | } 140 | 141 | } 142 | 143 | @objc func shutter(){ 144 | // Make sure capturePhotoOutput is valid 145 | if self.detectedFace == false { 146 | return 147 | } 148 | self.shutterBtn?.isEnabled = false 149 | guard let capturePhotoOutput = self.capturePhotoOutput else { return } 150 | // Get an instance of AVCapturePhotoSettings class 151 | let photoSettings = AVCapturePhotoSettings() 152 | // Set photo settings for our need 153 | photoSettings.isAutoStillImageStabilizationEnabled = true 154 | photoSettings.isHighResolutionPhotoEnabled = true 155 | photoSettings.flashMode = .auto 156 | // Call capturePhoto method by passing our photo settings and a 157 | // delegate implementing AVCapturePhotoCaptureDelegate 158 | capturePhotoOutput.capturePhoto(with: photoSettings, delegate: self) 159 | 160 | } 161 | 162 | @objc func checkPhotos(){ 163 | print("albumbtn clicked") 164 | let imagePickerController = UIImagePickerController() 165 | imagePickerController.delegate = self 166 | imagePickerController.sourceType = UIImagePickerControllerSourceType.savedPhotosAlbum 167 | imagePickerController.allowsEditing = true 168 | self.present(imagePickerController, animated: true, completion: nil) 169 | 170 | } 171 | 172 | override func viewWillAppear(_ animated: Bool) { 173 | super.viewWillAppear(animated) 174 | 175 | self.shutterBtn?.isEnabled = true 176 | 177 | sessionQueue.async { [unowned self] in 178 | switch self.setupResult { 179 | case .success: 180 | // Only setup observers and start the session running if setup succeeded. 181 | self.addObservers() 182 | self.session.startRunning() 183 | self.isSessionRunning = self.session.isRunning 184 | 185 | case .notAuthorized: 186 | DispatchQueue.main.async { [unowned self] in 187 | let message = NSLocalizedString("AVCamBarcode doesn't have permission to use the camera, please change privacy settings", comment: "Alert message when the user has denied access to the camera") 188 | let alertController = UIAlertController(title: "AppleFaceDetection", message: message, preferredStyle: .alert) 189 | alertController.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "Alert OK button"), style: .cancel, handler: nil)) 190 | alertController.addAction(UIAlertAction(title: NSLocalizedString("Settings", comment: "Alert button to open Settings"), style: .`default`, handler: { action in 191 | UIApplication.shared.open(URL(string: UIApplicationOpenSettingsURLString)!, options: [:], completionHandler: nil) 192 | })) 193 | 194 | self.present(alertController, animated: true, completion: nil) 195 | } 196 | 197 | case .configurationFailed: 198 | DispatchQueue.main.async { [unowned self] in 199 | let message = NSLocalizedString("Unable to capture media", comment: "Alert message when something goes wrong during capture session configuration") 200 | let alertController = UIAlertController(title: "AppleFaceDetection", message: message, preferredStyle: .alert) 201 | alertController.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "Alert OK button"), style: .cancel, handler: nil)) 202 | 203 | self.present(alertController, animated: true, completion: nil) 204 | } 205 | } 206 | } 207 | } 208 | 209 | override func viewWillDisappear(_ animated: Bool) { 210 | sessionQueue.async { [unowned self] in 211 | if self.setupResult == .success { 212 | self.session.stopRunning() 213 | self.isSessionRunning = self.session.isRunning 214 | self.removeObservers() 215 | } 216 | } 217 | 218 | super.viewWillDisappear(animated) 219 | } 220 | 221 | 222 | override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { 223 | super.viewWillTransition(to: size, with: coordinator) 224 | 225 | if let videoPreviewLayerConnection = previewView.videoPreviewLayer.connection { 226 | let deviceOrientation = UIDevice.current.orientation 227 | guard let newVideoOrientation = deviceOrientation.videoOrientation, deviceOrientation.isPortrait || deviceOrientation.isLandscape else { 228 | return 229 | } 230 | 231 | videoPreviewLayerConnection.videoOrientation = newVideoOrientation 232 | 233 | } 234 | } 235 | 236 | @objc func UpdateDetectionType(_ sender: UISegmentedControl) { 237 | // use segmentedControl to switch over VNRequest 238 | faceDetectionRequest = sender.selectedSegmentIndex == 0 ? VNDetectFaceRectanglesRequest(completionHandler: handleFaces) : VNDetectFaceLandmarksRequest(completionHandler: handleFaceLandmarks) 239 | 240 | setupVision() 241 | } 242 | 243 | 244 | // MARK: Session Management 245 | 246 | private enum SessionSetupResult { 247 | case success 248 | case notAuthorized 249 | case configurationFailed 250 | } 251 | 252 | private var devicePosition: AVCaptureDevice.Position = .back 253 | 254 | private let session = AVCaptureSession() 255 | private var isSessionRunning = false 256 | 257 | private let sessionQueue = DispatchQueue(label: "session queue", attributes: [], target: nil) // Communicate with the session and other session objects on this queue. 258 | 259 | private var setupResult: SessionSetupResult = .success 260 | 261 | private var videoDeviceInput: AVCaptureDeviceInput! 262 | 263 | private var videoDataOutput: AVCaptureVideoDataOutput! 264 | private var videoDataOutputQueue = DispatchQueue(label: "VideoDataOutputQueue") 265 | 266 | private var requests = [VNRequest]() 267 | 268 | private func configureSession() { 269 | if self.setupResult != .success { 270 | return 271 | } 272 | 273 | session.beginConfiguration() 274 | session.sessionPreset = .high 275 | 276 | // Add video input. 277 | do { 278 | var defaultVideoDevice: AVCaptureDevice? 279 | 280 | // Choose the back dual camera if available, otherwise default to a wide angle camera. 281 | if let dualCameraDevice = AVCaptureDevice.default(.builtInDualCamera, for: AVMediaType.video, position: .back) { 282 | defaultVideoDevice = dualCameraDevice 283 | } 284 | 285 | else if let backCameraDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: AVMediaType.video, position: .back) { 286 | defaultVideoDevice = backCameraDevice 287 | } 288 | 289 | else if let frontCameraDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: AVMediaType.video, position: .front) { 290 | defaultVideoDevice = frontCameraDevice 291 | } 292 | 293 | let videoDeviceInput = try AVCaptureDeviceInput(device: defaultVideoDevice!) 294 | 295 | if session.canAddInput(videoDeviceInput) { 296 | session.addInput(videoDeviceInput) 297 | self.videoDeviceInput = videoDeviceInput 298 | DispatchQueue.main.async { 299 | /* 300 | Why are we dispatching this to the main queue? 301 | Because AVCaptureVideoPreviewLayer is the backing layer for PreviewView and UIView 302 | can only be manipulated on the main thread. 303 | Note: As an exception to the above rule, it is not necessary to serialize video orientation changes 304 | on the AVCaptureVideoPreviewLayer’s connection with other session manipulation. 305 | 306 | Use the status bar orientation as the initial video orientation. Subsequent orientation changes are 307 | handled by CameraViewController.viewWillTransition(to:with:). 308 | */ 309 | let statusBarOrientation = UIApplication.shared.statusBarOrientation 310 | var initialVideoOrientation: AVCaptureVideoOrientation = .portrait 311 | if statusBarOrientation != .unknown { 312 | if let videoOrientation = statusBarOrientation.videoOrientation { 313 | initialVideoOrientation = videoOrientation 314 | } 315 | } 316 | self.previewView.videoPreviewLayer.connection!.videoOrientation = initialVideoOrientation 317 | } 318 | } 319 | 320 | else { 321 | print("Could not add video device input to the session") 322 | setupResult = .configurationFailed 323 | session.commitConfiguration() 324 | return 325 | } 326 | 327 | } 328 | catch { 329 | print("Could not create video device input: \(error)") 330 | setupResult = .configurationFailed 331 | session.commitConfiguration() 332 | return 333 | } 334 | 335 | // add output 336 | videoDataOutput = AVCaptureVideoDataOutput() 337 | videoDataOutput.videoSettings = [(kCVPixelBufferPixelFormatTypeKey as String): Int(kCVPixelFormatType_32BGRA)] 338 | 339 | 340 | if session.canAddOutput(videoDataOutput) { 341 | videoDataOutput.alwaysDiscardsLateVideoFrames = true 342 | videoDataOutput.setSampleBufferDelegate(self, queue: videoDataOutputQueue) 343 | session.addOutput(videoDataOutput) 344 | } 345 | else { 346 | print("Could not add metadata output to the session") 347 | setupResult = .configurationFailed 348 | session.commitConfiguration() 349 | return 350 | } 351 | 352 | session.commitConfiguration() 353 | 354 | } 355 | 356 | private func availableSessionPresets() -> [String] { 357 | let allSessionPresets = [AVCaptureSession.Preset.photo, 358 | AVCaptureSession.Preset.low, 359 | AVCaptureSession.Preset.medium, 360 | AVCaptureSession.Preset.high, 361 | AVCaptureSession.Preset.cif352x288, 362 | AVCaptureSession.Preset.vga640x480, 363 | AVCaptureSession.Preset.hd1280x720, 364 | AVCaptureSession.Preset.iFrame960x540, 365 | AVCaptureSession.Preset.iFrame1280x720, 366 | AVCaptureSession.Preset.hd1920x1080, 367 | AVCaptureSession.Preset.hd4K3840x2160] 368 | 369 | var availableSessionPresets = [String]() 370 | for sessionPreset in allSessionPresets { 371 | if session.canSetSessionPreset(sessionPreset) { 372 | availableSessionPresets.append(sessionPreset.rawValue) 373 | } 374 | } 375 | 376 | return availableSessionPresets 377 | } 378 | 379 | func exifOrientationFromDeviceOrientation() -> UInt32 { 380 | enum DeviceOrientation: UInt32 { 381 | case top0ColLeft = 1 382 | case top0ColRight = 2 383 | case bottom0ColRight = 3 384 | case bottom0ColLeft = 4 385 | case left0ColTop = 5 386 | case right0ColTop = 6 387 | case right0ColBottom = 7 388 | case left0ColBottom = 8 389 | } 390 | var exifOrientation: DeviceOrientation 391 | 392 | switch UIDevice.current.orientation { 393 | case .portraitUpsideDown: 394 | exifOrientation = .left0ColBottom 395 | case .landscapeLeft: 396 | exifOrientation = devicePosition == .front ? .bottom0ColRight : .top0ColLeft 397 | case .landscapeRight: 398 | exifOrientation = devicePosition == .front ? .top0ColLeft : .bottom0ColRight 399 | default: 400 | exifOrientation = .right0ColTop 401 | } 402 | return exifOrientation.rawValue 403 | } 404 | 405 | 406 | } 407 | 408 | extension ViewController { 409 | private func addObservers() { 410 | /* 411 | Observe the previewView's regionOfInterest to update the AVCaptureMetadataOutput's 412 | rectOfInterest when the user finishes resizing the region of interest. 413 | */ 414 | NotificationCenter.default.addObserver(self, selector: #selector(sessionRuntimeError), name: Notification.Name("AVCaptureSessionRuntimeErrorNotification"), object: session) 415 | 416 | /* 417 | A session can only run when the app is full screen. It will be interrupted 418 | in a multi-app layout, introduced in iOS 9, see also the documentation of 419 | AVCaptureSessionInterruptionReason. Add observers to handle these session 420 | interruptions and show a preview is paused message. See the documentation 421 | of AVCaptureSessionWasInterruptedNotification for other interruption reasons. 422 | */ 423 | NotificationCenter.default.addObserver(self, selector: #selector(sessionWasInterrupted), name: Notification.Name("AVCaptureSessionWasInterruptedNotification"), object: session) 424 | NotificationCenter.default.addObserver(self, selector: #selector(sessionInterruptionEnded), name: Notification.Name("AVCaptureSessionInterruptionEndedNotification"), object: session) 425 | } 426 | 427 | private func removeObservers() { 428 | NotificationCenter.default.removeObserver(self) 429 | } 430 | 431 | @objc func sessionRuntimeError(_ notification: Notification) { 432 | guard let errorValue = notification.userInfo?[AVCaptureSessionErrorKey] as? NSError else { return } 433 | 434 | let error = AVError(_nsError: errorValue) 435 | print("Capture session runtime error: \(error)") 436 | 437 | /* 438 | Automatically try to restart the session running if media services were 439 | reset and the last start running succeeded. Otherwise, enable the user 440 | to try to resume the session running. 441 | */ 442 | if error.code == .mediaServicesWereReset { 443 | sessionQueue.async { [unowned self] in 444 | if self.isSessionRunning { 445 | self.session.startRunning() 446 | self.isSessionRunning = self.session.isRunning 447 | } 448 | } 449 | } 450 | } 451 | 452 | @objc func sessionWasInterrupted(_ notification: Notification) { 453 | /* 454 | In some scenarios we want to enable the user to resume the session running. 455 | For example, if music playback is initiated via control center while 456 | using AVCamBarcode, then the user can let AVCamBarcode resume 457 | the session running, which will stop music playback. Note that stopping 458 | music playback in control center will not automatically resume the session 459 | running. Also note that it is not always possible to resume, see `resumeInterruptedSession(_:)`. 460 | */ 461 | if let userInfoValue = notification.userInfo?[AVCaptureSessionInterruptionReasonKey] as AnyObject?, let reasonIntegerValue = userInfoValue.integerValue, let reason = AVCaptureSession.InterruptionReason(rawValue: reasonIntegerValue) { 462 | print("Capture session was interrupted with reason \(reason)") 463 | } 464 | } 465 | 466 | @objc func sessionInterruptionEnded(_ notification: Notification) { 467 | print("Capture session interruption ended") 468 | } 469 | } 470 | 471 | extension ViewController { 472 | func setupVision() { 473 | self.requests = [faceDetectionRequest] 474 | } 475 | 476 | func handleFaces(request: VNRequest, error: Error?) { 477 | DispatchQueue.main.async { 478 | //perform all the UI updates on the main queue 479 | guard let results = request.results as? [VNFaceObservation] else { return } 480 | self.previewView.removeMask() 481 | if results.count > 0 { 482 | self.detectedFace = true 483 | self.firstFace = results[0] 484 | } 485 | else { 486 | self.detectedFace = false 487 | } 488 | for face in results { 489 | self.previewView.drawFaceboundingBox(face: face) 490 | } 491 | } 492 | } 493 | 494 | func handleFaceLandmarks(request: VNRequest, error: Error?) { 495 | DispatchQueue.main.async { 496 | //perform all the UI updates on the main queue 497 | guard let results = request.results as? [VNFaceObservation] else { return } 498 | self.previewView.removeMask() 499 | if results.count > 0 { 500 | self.detectedFace = true 501 | self.firstFace = results[0] 502 | } 503 | else { 504 | self.detectedFace = false 505 | } 506 | for face in results { 507 | self.previewView.drawFaceWithLandmarks(face: face) 508 | } 509 | } 510 | } 511 | 512 | 513 | } 514 | 515 | extension ViewController: AVCaptureVideoDataOutputSampleBufferDelegate{ 516 | // MARK: - AVCaptureVideoDataOutputSampleBufferDelegate 517 | func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { 518 | guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer), 519 | let exifOrientation = CGImagePropertyOrientation(rawValue: exifOrientationFromDeviceOrientation()) else { return } 520 | var requestOptions: [VNImageOption : Any] = [:] 521 | 522 | if let cameraIntrinsicData = CMGetAttachment(sampleBuffer, kCMSampleBufferAttachmentKey_CameraIntrinsicMatrix, nil) { 523 | requestOptions = [.cameraIntrinsics : cameraIntrinsicData] 524 | } 525 | 526 | let imageRequestHandler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer, orientation: exifOrientation, options: requestOptions) 527 | 528 | do { 529 | try imageRequestHandler.perform(requests) 530 | } 531 | 532 | catch { 533 | print(error) 534 | } 535 | 536 | } 537 | 538 | } 539 | 540 | extension ViewController: AVCapturePhotoCaptureDelegate { 541 | func photoOutput(_ captureOutput: AVCapturePhotoOutput, 542 | didFinishProcessingPhoto photoSampleBuffer: CMSampleBuffer?, 543 | previewPhoto previewPhotoSampleBuffer: CMSampleBuffer?, 544 | resolvedSettings: AVCaptureResolvedPhotoSettings, 545 | bracketSettings: AVCaptureBracketedStillImageSettings?, 546 | error: Error?) { 547 | // get captured image 548 | // Make sure we get some photo sample buffer 549 | guard error == nil, let photoSampleBuffer = photoSampleBuffer else { 550 | print("Error capturing photo: \(String(describing: error))") 551 | return 552 | } 553 | // Convert photo same buffer to a jpeg image data by using // AVCapturePhotoOutput 554 | guard let imageData = 555 | AVCapturePhotoOutput.jpegPhotoDataRepresentation(forJPEGSampleBuffer: photoSampleBuffer, previewPhotoSampleBuffer: previewPhotoSampleBuffer) else { 556 | return 557 | } 558 | // Initialise a UIImage with our image data 559 | let capturedImage = UIImage.init(data: imageData , scale: 1.0) 560 | 561 | let processViewController = ProcessViewController() 562 | processViewController.faceImg = capturedImage 563 | 564 | if let face = self.firstFace { 565 | let oldbounds = face.boundingBox 566 | let transform = CGAffineTransform(scaleX: 1, y: -1).translatedBy(x: 0, y: -(capturedImage?.size.height)!) 567 | let translate = CGAffineTransform.identity.scaledBy(x: (capturedImage?.size.width)!, y: (capturedImage?.size.height)!) 568 | let facebounds = face.boundingBox.applying(translate).applying(transform) 569 | 570 | processViewController.scaleRect = facebounds 571 | } 572 | self.navigationController?.pushViewController(processViewController, animated: true) 573 | // if let image = capturedImage { 574 | // // Save our captured image to photos album 575 | // UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil) 576 | // } 577 | } 578 | } 579 | 580 | extension ViewController: UINavigationControllerDelegate, UIImagePickerControllerDelegate { 581 | func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) { 582 | if let pickedImage = info[UIImagePickerControllerEditedImage] as? UIImage { 583 | let processViewController = ProcessViewController() 584 | processViewController.faceImg = pickedImage 585 | self.navigationController?.pushViewController(processViewController, animated: true) 586 | } 587 | 588 | dismiss(animated: true, completion: nil) 589 | } 590 | } 591 | 592 | extension UIDeviceOrientation { 593 | var videoOrientation: AVCaptureVideoOrientation? { 594 | switch self { 595 | case .portrait: return .portrait 596 | case .portraitUpsideDown: return .portraitUpsideDown 597 | case .landscapeLeft: return .landscapeRight 598 | case .landscapeRight: return .landscapeLeft 599 | default: return nil 600 | } 601 | } 602 | } 603 | 604 | extension UIInterfaceOrientation { 605 | var videoOrientation: AVCaptureVideoOrientation? { 606 | switch self { 607 | case .portrait: return .portrait 608 | case .portraitUpsideDown: return .portraitUpsideDown 609 | case .landscapeLeft: return .landscapeLeft 610 | case .landscapeRight: return .landscapeRight 611 | default: return nil 612 | } 613 | } 614 | } 615 | 616 | extension UIImage { 617 | func crop( rect: CGRect) -> UIImage { 618 | let imageWidth = self.size.width 619 | // let imageHeight = self.size.height 620 | 621 | var rect = rect 622 | var x = rect.origin.x * self.scale 623 | var y = rect.origin.y * self.scale 624 | var width = rect.size.width * self.scale 625 | var height = rect.size.height * self.scale 626 | 627 | var offsetX: CGFloat = 0 628 | var offsetY: CGFloat = 0 629 | let scaleOffsetX: CGFloat = 0.2 630 | let scaleOffsetY: CGFloat = 0.2 631 | 632 | if x - width * scaleOffsetX > 0 { 633 | offsetX = width * scaleOffsetX 634 | x = x - offsetX 635 | } else { 636 | offsetX = 0 637 | x = 0 638 | } 639 | if y - height * scaleOffsetY > 0 { 640 | offsetY = height * scaleOffsetY 641 | y = y - offsetY 642 | } else { 643 | offsetY = 0 644 | x = 0 645 | } 646 | 647 | width += offsetX * 2 648 | height += offsetY * 2 649 | 650 | let tempY = y 651 | y = imageWidth - (x + width) 652 | x = tempY 653 | 654 | rect.origin.x = x 655 | rect.origin.y = y 656 | rect.size.width = height 657 | rect.size.height = width 658 | // rect.size.width = width 659 | // rect.size.height = width 660 | 661 | 662 | let imageRef = self.cgImage!.cropping(to: rect) 663 | let image = UIImage(cgImage: imageRef!, scale: self.scale, orientation: self.imageOrientation) 664 | return image 665 | } 666 | } 667 | -------------------------------------------------------------------------------- /FaceApp.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 2D334CC020AFE0C300EF7AB5 /* PreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D334CBE20AFE0C200EF7AB5 /* PreviewView.swift */; }; 11 | 2D334CC120AFE0C300EF7AB5 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D334CBF20AFE0C300EF7AB5 /* ViewController.swift */; }; 12 | 2D5590F220A96FF500AA2209 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2D5590F120A96FF500AA2209 /* Assets.xcassets */; }; 13 | 2D59248B20AA9A1A00AB941D /* HorizontalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D59248A20AA9A1A00AB941D /* HorizontalViewController.swift */; }; 14 | 2D59248D20AAA25F00AB941D /* MyCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D59248C20AAA25F00AB941D /* MyCollectionViewCell.swift */; }; 15 | 2D60423120A842D40078C039 /* ProcessViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D60423020A842D40078C039 /* ProcessViewController.swift */; }; 16 | 2D60423520A86EE70078C039 /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D60423420A86EE70078C039 /* TableViewController.swift */; }; 17 | 2D77DC14209F56670022BAAB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D77DC13209F56670022BAAB /* AppDelegate.swift */; }; 18 | 2D77DC16209F56670022BAAB /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D77DC15209F56670022BAAB /* LoginViewController.swift */; }; 19 | 2D77DC19209F56670022BAAB /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2D77DC17209F56670022BAAB /* Main.storyboard */; }; 20 | 2D77DC1E209F56680022BAAB /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2D77DC1C209F56680022BAAB /* LaunchScreen.storyboard */; }; 21 | 2D77DC29209F56680022BAAB /* FaceAppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D77DC28209F56680022BAAB /* FaceAppTests.swift */; }; 22 | 2D77DC34209F56680022BAAB /* FaceAppUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D77DC33209F56680022BAAB /* FaceAppUITests.swift */; }; 23 | 2D77DC46209F61060022BAAB /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D77DC45209F61060022BAAB /* MainViewController.swift */; }; 24 | 8BE044749F4F3EDD6923F5ED /* Pods_FaceApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 412112E8EFF4697B1C640E36 /* Pods_FaceApp.framework */; }; 25 | /* End PBXBuildFile section */ 26 | 27 | /* Begin PBXContainerItemProxy section */ 28 | 2D77DC25209F56680022BAAB /* PBXContainerItemProxy */ = { 29 | isa = PBXContainerItemProxy; 30 | containerPortal = 2D77DC08209F56670022BAAB /* Project object */; 31 | proxyType = 1; 32 | remoteGlobalIDString = 2D77DC0F209F56670022BAAB; 33 | remoteInfo = FaceApp; 34 | }; 35 | 2D77DC30209F56680022BAAB /* PBXContainerItemProxy */ = { 36 | isa = PBXContainerItemProxy; 37 | containerPortal = 2D77DC08209F56670022BAAB /* Project object */; 38 | proxyType = 1; 39 | remoteGlobalIDString = 2D77DC0F209F56670022BAAB; 40 | remoteInfo = FaceApp; 41 | }; 42 | /* End PBXContainerItemProxy section */ 43 | 44 | /* Begin PBXFileReference section */ 45 | 1532E256725320EADEF5CF9A /* Pods-FaceApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FaceApp.debug.xcconfig"; path = "Pods/Target Support Files/Pods-FaceApp/Pods-FaceApp.debug.xcconfig"; sourceTree = ""; }; 46 | 2D334CBE20AFE0C200EF7AB5 /* PreviewView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreviewView.swift; sourceTree = ""; }; 47 | 2D334CBF20AFE0C300EF7AB5 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 48 | 2D5590F120A96FF500AA2209 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 49 | 2D59248A20AA9A1A00AB941D /* HorizontalViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HorizontalViewController.swift; sourceTree = ""; }; 50 | 2D59248C20AAA25F00AB941D /* MyCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyCollectionViewCell.swift; sourceTree = ""; }; 51 | 2D60423020A842D40078C039 /* ProcessViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProcessViewController.swift; sourceTree = ""; }; 52 | 2D60423420A86EE70078C039 /* TableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = ""; }; 53 | 2D77DC10209F56670022BAAB /* FaceApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FaceApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | 2D77DC13209F56670022BAAB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 55 | 2D77DC15209F56670022BAAB /* LoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; 56 | 2D77DC18209F56670022BAAB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 57 | 2D77DC1D209F56680022BAAB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 58 | 2D77DC1F209F56680022BAAB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 59 | 2D77DC24209F56680022BAAB /* FaceAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FaceAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 60 | 2D77DC28209F56680022BAAB /* FaceAppTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaceAppTests.swift; sourceTree = ""; }; 61 | 2D77DC2A209F56680022BAAB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 62 | 2D77DC2F209F56680022BAAB /* FaceAppUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FaceAppUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 63 | 2D77DC33209F56680022BAAB /* FaceAppUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaceAppUITests.swift; sourceTree = ""; }; 64 | 2D77DC35209F56680022BAAB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 65 | 2D77DC45209F61060022BAAB /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; 66 | 412112E8EFF4697B1C640E36 /* Pods_FaceApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FaceApp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 67 | BCA8B798D961322553D4C940 /* Pods-FaceApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FaceApp.release.xcconfig"; path = "Pods/Target Support Files/Pods-FaceApp/Pods-FaceApp.release.xcconfig"; sourceTree = ""; }; 68 | /* End PBXFileReference section */ 69 | 70 | /* Begin PBXFrameworksBuildPhase section */ 71 | 2D77DC0D209F56670022BAAB /* Frameworks */ = { 72 | isa = PBXFrameworksBuildPhase; 73 | buildActionMask = 2147483647; 74 | files = ( 75 | 8BE044749F4F3EDD6923F5ED /* Pods_FaceApp.framework in Frameworks */, 76 | ); 77 | runOnlyForDeploymentPostprocessing = 0; 78 | }; 79 | 2D77DC21209F56680022BAAB /* Frameworks */ = { 80 | isa = PBXFrameworksBuildPhase; 81 | buildActionMask = 2147483647; 82 | files = ( 83 | ); 84 | runOnlyForDeploymentPostprocessing = 0; 85 | }; 86 | 2D77DC2C209F56680022BAAB /* Frameworks */ = { 87 | isa = PBXFrameworksBuildPhase; 88 | buildActionMask = 2147483647; 89 | files = ( 90 | ); 91 | runOnlyForDeploymentPostprocessing = 0; 92 | }; 93 | /* End PBXFrameworksBuildPhase section */ 94 | 95 | /* Begin PBXGroup section */ 96 | 2D77DC07209F56670022BAAB = { 97 | isa = PBXGroup; 98 | children = ( 99 | 2D77DC12209F56670022BAAB /* FaceApp */, 100 | 2D77DC27209F56680022BAAB /* FaceAppTests */, 101 | 2D77DC32209F56680022BAAB /* FaceAppUITests */, 102 | 2D77DC11209F56670022BAAB /* Products */, 103 | D841439D0BFFE56230927713 /* Pods */, 104 | 3E824493AE7089A50E12F62F /* Frameworks */, 105 | ); 106 | sourceTree = ""; 107 | }; 108 | 2D77DC11209F56670022BAAB /* Products */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | 2D77DC10209F56670022BAAB /* FaceApp.app */, 112 | 2D77DC24209F56680022BAAB /* FaceAppTests.xctest */, 113 | 2D77DC2F209F56680022BAAB /* FaceAppUITests.xctest */, 114 | ); 115 | name = Products; 116 | sourceTree = ""; 117 | }; 118 | 2D77DC12209F56670022BAAB /* FaceApp */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 2D77DC13209F56670022BAAB /* AppDelegate.swift */, 122 | 2D77DC15209F56670022BAAB /* LoginViewController.swift */, 123 | 2D77DC45209F61060022BAAB /* MainViewController.swift */, 124 | 2D334CBE20AFE0C200EF7AB5 /* PreviewView.swift */, 125 | 2D334CBF20AFE0C300EF7AB5 /* ViewController.swift */, 126 | 2D60423020A842D40078C039 /* ProcessViewController.swift */, 127 | 2D77DC17209F56670022BAAB /* Main.storyboard */, 128 | 2D77DC1C209F56680022BAAB /* LaunchScreen.storyboard */, 129 | 2D5590F120A96FF500AA2209 /* Assets.xcassets */, 130 | 2D77DC1F209F56680022BAAB /* Info.plist */, 131 | 2D60423420A86EE70078C039 /* TableViewController.swift */, 132 | 2D59248A20AA9A1A00AB941D /* HorizontalViewController.swift */, 133 | 2D59248C20AAA25F00AB941D /* MyCollectionViewCell.swift */, 134 | ); 135 | path = FaceApp; 136 | sourceTree = ""; 137 | }; 138 | 2D77DC27209F56680022BAAB /* FaceAppTests */ = { 139 | isa = PBXGroup; 140 | children = ( 141 | 2D77DC28209F56680022BAAB /* FaceAppTests.swift */, 142 | 2D77DC2A209F56680022BAAB /* Info.plist */, 143 | ); 144 | path = FaceAppTests; 145 | sourceTree = ""; 146 | }; 147 | 2D77DC32209F56680022BAAB /* FaceAppUITests */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | 2D77DC33209F56680022BAAB /* FaceAppUITests.swift */, 151 | 2D77DC35209F56680022BAAB /* Info.plist */, 152 | ); 153 | path = FaceAppUITests; 154 | sourceTree = ""; 155 | }; 156 | 3E824493AE7089A50E12F62F /* Frameworks */ = { 157 | isa = PBXGroup; 158 | children = ( 159 | 412112E8EFF4697B1C640E36 /* Pods_FaceApp.framework */, 160 | ); 161 | name = Frameworks; 162 | sourceTree = ""; 163 | }; 164 | D841439D0BFFE56230927713 /* Pods */ = { 165 | isa = PBXGroup; 166 | children = ( 167 | 1532E256725320EADEF5CF9A /* Pods-FaceApp.debug.xcconfig */, 168 | BCA8B798D961322553D4C940 /* Pods-FaceApp.release.xcconfig */, 169 | ); 170 | name = Pods; 171 | sourceTree = ""; 172 | }; 173 | /* End PBXGroup section */ 174 | 175 | /* Begin PBXNativeTarget section */ 176 | 2D77DC0F209F56670022BAAB /* FaceApp */ = { 177 | isa = PBXNativeTarget; 178 | buildConfigurationList = 2D77DC38209F56680022BAAB /* Build configuration list for PBXNativeTarget "FaceApp" */; 179 | buildPhases = ( 180 | 99794F7F2FD9FB6C8E9FB5A7 /* [CP] Check Pods Manifest.lock */, 181 | 2D77DC0C209F56670022BAAB /* Sources */, 182 | 2D77DC0D209F56670022BAAB /* Frameworks */, 183 | 2D77DC0E209F56670022BAAB /* Resources */, 184 | C522967D07D1AB147F4AD02E /* [CP] Embed Pods Frameworks */, 185 | ); 186 | buildRules = ( 187 | ); 188 | dependencies = ( 189 | ); 190 | name = FaceApp; 191 | productName = FaceApp; 192 | productReference = 2D77DC10209F56670022BAAB /* FaceApp.app */; 193 | productType = "com.apple.product-type.application"; 194 | }; 195 | 2D77DC23209F56680022BAAB /* FaceAppTests */ = { 196 | isa = PBXNativeTarget; 197 | buildConfigurationList = 2D77DC3B209F56680022BAAB /* Build configuration list for PBXNativeTarget "FaceAppTests" */; 198 | buildPhases = ( 199 | 2D77DC20209F56680022BAAB /* Sources */, 200 | 2D77DC21209F56680022BAAB /* Frameworks */, 201 | 2D77DC22209F56680022BAAB /* Resources */, 202 | ); 203 | buildRules = ( 204 | ); 205 | dependencies = ( 206 | 2D77DC26209F56680022BAAB /* PBXTargetDependency */, 207 | ); 208 | name = FaceAppTests; 209 | productName = FaceAppTests; 210 | productReference = 2D77DC24209F56680022BAAB /* FaceAppTests.xctest */; 211 | productType = "com.apple.product-type.bundle.unit-test"; 212 | }; 213 | 2D77DC2E209F56680022BAAB /* FaceAppUITests */ = { 214 | isa = PBXNativeTarget; 215 | buildConfigurationList = 2D77DC3E209F56680022BAAB /* Build configuration list for PBXNativeTarget "FaceAppUITests" */; 216 | buildPhases = ( 217 | 2D77DC2B209F56680022BAAB /* Sources */, 218 | 2D77DC2C209F56680022BAAB /* Frameworks */, 219 | 2D77DC2D209F56680022BAAB /* Resources */, 220 | ); 221 | buildRules = ( 222 | ); 223 | dependencies = ( 224 | 2D77DC31209F56680022BAAB /* PBXTargetDependency */, 225 | ); 226 | name = FaceAppUITests; 227 | productName = FaceAppUITests; 228 | productReference = 2D77DC2F209F56680022BAAB /* FaceAppUITests.xctest */; 229 | productType = "com.apple.product-type.bundle.ui-testing"; 230 | }; 231 | /* End PBXNativeTarget section */ 232 | 233 | /* Begin PBXProject section */ 234 | 2D77DC08209F56670022BAAB /* Project object */ = { 235 | isa = PBXProject; 236 | attributes = { 237 | LastSwiftUpdateCheck = 0930; 238 | LastUpgradeCheck = 0930; 239 | ORGANIZATIONNAME = c; 240 | TargetAttributes = { 241 | 2D77DC0F209F56670022BAAB = { 242 | CreatedOnToolsVersion = 9.3; 243 | }; 244 | 2D77DC23209F56680022BAAB = { 245 | CreatedOnToolsVersion = 9.3; 246 | TestTargetID = 2D77DC0F209F56670022BAAB; 247 | }; 248 | 2D77DC2E209F56680022BAAB = { 249 | CreatedOnToolsVersion = 9.3; 250 | TestTargetID = 2D77DC0F209F56670022BAAB; 251 | }; 252 | }; 253 | }; 254 | buildConfigurationList = 2D77DC0B209F56670022BAAB /* Build configuration list for PBXProject "FaceApp" */; 255 | compatibilityVersion = "Xcode 9.3"; 256 | developmentRegion = en; 257 | hasScannedForEncodings = 0; 258 | knownRegions = ( 259 | en, 260 | Base, 261 | ); 262 | mainGroup = 2D77DC07209F56670022BAAB; 263 | productRefGroup = 2D77DC11209F56670022BAAB /* Products */; 264 | projectDirPath = ""; 265 | projectRoot = ""; 266 | targets = ( 267 | 2D77DC0F209F56670022BAAB /* FaceApp */, 268 | 2D77DC23209F56680022BAAB /* FaceAppTests */, 269 | 2D77DC2E209F56680022BAAB /* FaceAppUITests */, 270 | ); 271 | }; 272 | /* End PBXProject section */ 273 | 274 | /* Begin PBXResourcesBuildPhase section */ 275 | 2D77DC0E209F56670022BAAB /* Resources */ = { 276 | isa = PBXResourcesBuildPhase; 277 | buildActionMask = 2147483647; 278 | files = ( 279 | 2D77DC1E209F56680022BAAB /* LaunchScreen.storyboard in Resources */, 280 | 2D5590F220A96FF500AA2209 /* Assets.xcassets in Resources */, 281 | 2D77DC19209F56670022BAAB /* Main.storyboard in Resources */, 282 | ); 283 | runOnlyForDeploymentPostprocessing = 0; 284 | }; 285 | 2D77DC22209F56680022BAAB /* Resources */ = { 286 | isa = PBXResourcesBuildPhase; 287 | buildActionMask = 2147483647; 288 | files = ( 289 | ); 290 | runOnlyForDeploymentPostprocessing = 0; 291 | }; 292 | 2D77DC2D209F56680022BAAB /* Resources */ = { 293 | isa = PBXResourcesBuildPhase; 294 | buildActionMask = 2147483647; 295 | files = ( 296 | ); 297 | runOnlyForDeploymentPostprocessing = 0; 298 | }; 299 | /* End PBXResourcesBuildPhase section */ 300 | 301 | /* Begin PBXShellScriptBuildPhase section */ 302 | 99794F7F2FD9FB6C8E9FB5A7 /* [CP] Check Pods Manifest.lock */ = { 303 | isa = PBXShellScriptBuildPhase; 304 | buildActionMask = 2147483647; 305 | files = ( 306 | ); 307 | inputPaths = ( 308 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 309 | "${PODS_ROOT}/Manifest.lock", 310 | ); 311 | name = "[CP] Check Pods Manifest.lock"; 312 | outputPaths = ( 313 | "$(DERIVED_FILE_DIR)/Pods-FaceApp-checkManifestLockResult.txt", 314 | ); 315 | runOnlyForDeploymentPostprocessing = 0; 316 | shellPath = /bin/sh; 317 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 318 | showEnvVarsInLog = 0; 319 | }; 320 | C522967D07D1AB147F4AD02E /* [CP] Embed Pods Frameworks */ = { 321 | isa = PBXShellScriptBuildPhase; 322 | buildActionMask = 2147483647; 323 | files = ( 324 | ); 325 | inputPaths = ( 326 | "${SRCROOT}/Pods/Target Support Files/Pods-FaceApp/Pods-FaceApp-frameworks.sh", 327 | "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework", 328 | "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework", 329 | "${BUILT_PRODUCTS_DIR}/SwiftyJSON/SwiftyJSON.framework", 330 | "${BUILT_PRODUCTS_DIR}/Toast-Swift/Toast_Swift.framework", 331 | ); 332 | name = "[CP] Embed Pods Frameworks"; 333 | outputPaths = ( 334 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework", 335 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework", 336 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyJSON.framework", 337 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Toast_Swift.framework", 338 | ); 339 | runOnlyForDeploymentPostprocessing = 0; 340 | shellPath = /bin/sh; 341 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-FaceApp/Pods-FaceApp-frameworks.sh\"\n"; 342 | showEnvVarsInLog = 0; 343 | }; 344 | /* End PBXShellScriptBuildPhase section */ 345 | 346 | /* Begin PBXSourcesBuildPhase section */ 347 | 2D77DC0C209F56670022BAAB /* Sources */ = { 348 | isa = PBXSourcesBuildPhase; 349 | buildActionMask = 2147483647; 350 | files = ( 351 | 2D60423120A842D40078C039 /* ProcessViewController.swift in Sources */, 352 | 2D334CC020AFE0C300EF7AB5 /* PreviewView.swift in Sources */, 353 | 2D59248D20AAA25F00AB941D /* MyCollectionViewCell.swift in Sources */, 354 | 2D334CC120AFE0C300EF7AB5 /* ViewController.swift in Sources */, 355 | 2D77DC46209F61060022BAAB /* MainViewController.swift in Sources */, 356 | 2D77DC16209F56670022BAAB /* LoginViewController.swift in Sources */, 357 | 2D60423520A86EE70078C039 /* TableViewController.swift in Sources */, 358 | 2D59248B20AA9A1A00AB941D /* HorizontalViewController.swift in Sources */, 359 | 2D77DC14209F56670022BAAB /* AppDelegate.swift in Sources */, 360 | ); 361 | runOnlyForDeploymentPostprocessing = 0; 362 | }; 363 | 2D77DC20209F56680022BAAB /* Sources */ = { 364 | isa = PBXSourcesBuildPhase; 365 | buildActionMask = 2147483647; 366 | files = ( 367 | 2D77DC29209F56680022BAAB /* FaceAppTests.swift in Sources */, 368 | ); 369 | runOnlyForDeploymentPostprocessing = 0; 370 | }; 371 | 2D77DC2B209F56680022BAAB /* Sources */ = { 372 | isa = PBXSourcesBuildPhase; 373 | buildActionMask = 2147483647; 374 | files = ( 375 | 2D77DC34209F56680022BAAB /* FaceAppUITests.swift in Sources */, 376 | ); 377 | runOnlyForDeploymentPostprocessing = 0; 378 | }; 379 | /* End PBXSourcesBuildPhase section */ 380 | 381 | /* Begin PBXTargetDependency section */ 382 | 2D77DC26209F56680022BAAB /* PBXTargetDependency */ = { 383 | isa = PBXTargetDependency; 384 | target = 2D77DC0F209F56670022BAAB /* FaceApp */; 385 | targetProxy = 2D77DC25209F56680022BAAB /* PBXContainerItemProxy */; 386 | }; 387 | 2D77DC31209F56680022BAAB /* PBXTargetDependency */ = { 388 | isa = PBXTargetDependency; 389 | target = 2D77DC0F209F56670022BAAB /* FaceApp */; 390 | targetProxy = 2D77DC30209F56680022BAAB /* PBXContainerItemProxy */; 391 | }; 392 | /* End PBXTargetDependency section */ 393 | 394 | /* Begin PBXVariantGroup section */ 395 | 2D77DC17209F56670022BAAB /* Main.storyboard */ = { 396 | isa = PBXVariantGroup; 397 | children = ( 398 | 2D77DC18209F56670022BAAB /* Base */, 399 | ); 400 | name = Main.storyboard; 401 | sourceTree = ""; 402 | }; 403 | 2D77DC1C209F56680022BAAB /* LaunchScreen.storyboard */ = { 404 | isa = PBXVariantGroup; 405 | children = ( 406 | 2D77DC1D209F56680022BAAB /* Base */, 407 | ); 408 | name = LaunchScreen.storyboard; 409 | sourceTree = ""; 410 | }; 411 | /* End PBXVariantGroup section */ 412 | 413 | /* Begin XCBuildConfiguration section */ 414 | 2D77DC36209F56680022BAAB /* Debug */ = { 415 | isa = XCBuildConfiguration; 416 | buildSettings = { 417 | ALWAYS_SEARCH_USER_PATHS = NO; 418 | CLANG_ANALYZER_NONNULL = YES; 419 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 420 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 421 | CLANG_CXX_LIBRARY = "libc++"; 422 | CLANG_ENABLE_MODULES = YES; 423 | CLANG_ENABLE_OBJC_ARC = YES; 424 | CLANG_ENABLE_OBJC_WEAK = YES; 425 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 426 | CLANG_WARN_BOOL_CONVERSION = YES; 427 | CLANG_WARN_COMMA = YES; 428 | CLANG_WARN_CONSTANT_CONVERSION = YES; 429 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 430 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 431 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 432 | CLANG_WARN_EMPTY_BODY = YES; 433 | CLANG_WARN_ENUM_CONVERSION = YES; 434 | CLANG_WARN_INFINITE_RECURSION = YES; 435 | CLANG_WARN_INT_CONVERSION = YES; 436 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 437 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 438 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 439 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 440 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 441 | CLANG_WARN_STRICT_PROTOTYPES = YES; 442 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 443 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 444 | CLANG_WARN_UNREACHABLE_CODE = YES; 445 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 446 | CODE_SIGN_IDENTITY = "iPhone Developer"; 447 | COPY_PHASE_STRIP = NO; 448 | DEBUG_INFORMATION_FORMAT = dwarf; 449 | ENABLE_STRICT_OBJC_MSGSEND = YES; 450 | ENABLE_TESTABILITY = YES; 451 | GCC_C_LANGUAGE_STANDARD = gnu11; 452 | GCC_DYNAMIC_NO_PIC = NO; 453 | GCC_NO_COMMON_BLOCKS = YES; 454 | GCC_OPTIMIZATION_LEVEL = 0; 455 | GCC_PREPROCESSOR_DEFINITIONS = ( 456 | "DEBUG=1", 457 | "$(inherited)", 458 | ); 459 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 460 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 461 | GCC_WARN_UNDECLARED_SELECTOR = YES; 462 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 463 | GCC_WARN_UNUSED_FUNCTION = YES; 464 | GCC_WARN_UNUSED_VARIABLE = YES; 465 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 466 | MTL_ENABLE_DEBUG_INFO = YES; 467 | ONLY_ACTIVE_ARCH = YES; 468 | SDKROOT = iphoneos; 469 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 470 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 471 | }; 472 | name = Debug; 473 | }; 474 | 2D77DC37209F56680022BAAB /* Release */ = { 475 | isa = XCBuildConfiguration; 476 | buildSettings = { 477 | ALWAYS_SEARCH_USER_PATHS = NO; 478 | CLANG_ANALYZER_NONNULL = YES; 479 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 480 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 481 | CLANG_CXX_LIBRARY = "libc++"; 482 | CLANG_ENABLE_MODULES = YES; 483 | CLANG_ENABLE_OBJC_ARC = YES; 484 | CLANG_ENABLE_OBJC_WEAK = YES; 485 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 486 | CLANG_WARN_BOOL_CONVERSION = YES; 487 | CLANG_WARN_COMMA = YES; 488 | CLANG_WARN_CONSTANT_CONVERSION = YES; 489 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 490 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 491 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 492 | CLANG_WARN_EMPTY_BODY = YES; 493 | CLANG_WARN_ENUM_CONVERSION = YES; 494 | CLANG_WARN_INFINITE_RECURSION = YES; 495 | CLANG_WARN_INT_CONVERSION = YES; 496 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 497 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 498 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 499 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 500 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 501 | CLANG_WARN_STRICT_PROTOTYPES = YES; 502 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 503 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 504 | CLANG_WARN_UNREACHABLE_CODE = YES; 505 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 506 | CODE_SIGN_IDENTITY = "iPhone Developer"; 507 | COPY_PHASE_STRIP = NO; 508 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 509 | ENABLE_NS_ASSERTIONS = NO; 510 | ENABLE_STRICT_OBJC_MSGSEND = YES; 511 | GCC_C_LANGUAGE_STANDARD = gnu11; 512 | GCC_NO_COMMON_BLOCKS = YES; 513 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 514 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 515 | GCC_WARN_UNDECLARED_SELECTOR = YES; 516 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 517 | GCC_WARN_UNUSED_FUNCTION = YES; 518 | GCC_WARN_UNUSED_VARIABLE = YES; 519 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 520 | MTL_ENABLE_DEBUG_INFO = NO; 521 | SDKROOT = iphoneos; 522 | SWIFT_COMPILATION_MODE = wholemodule; 523 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 524 | VALIDATE_PRODUCT = YES; 525 | }; 526 | name = Release; 527 | }; 528 | 2D77DC39209F56680022BAAB /* Debug */ = { 529 | isa = XCBuildConfiguration; 530 | baseConfigurationReference = 1532E256725320EADEF5CF9A /* Pods-FaceApp.debug.xcconfig */; 531 | buildSettings = { 532 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 533 | CODE_SIGN_STYLE = Automatic; 534 | DEVELOPMENT_TEAM = GY384L655Q; 535 | INFOPLIST_FILE = FaceApp/Info.plist; 536 | IPHONEOS_DEPLOYMENT_TARGET = 11.3; 537 | LD_RUNPATH_SEARCH_PATHS = ( 538 | "$(inherited)", 539 | "@executable_path/Frameworks", 540 | ); 541 | PRODUCT_BUNDLE_IDENTIFIER = c.arcobaleno.FaceApp; 542 | PRODUCT_NAME = "$(TARGET_NAME)"; 543 | SWIFT_VERSION = 4.0; 544 | TARGETED_DEVICE_FAMILY = "1,2"; 545 | }; 546 | name = Debug; 547 | }; 548 | 2D77DC3A209F56680022BAAB /* Release */ = { 549 | isa = XCBuildConfiguration; 550 | baseConfigurationReference = BCA8B798D961322553D4C940 /* Pods-FaceApp.release.xcconfig */; 551 | buildSettings = { 552 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 553 | CODE_SIGN_STYLE = Automatic; 554 | DEVELOPMENT_TEAM = GY384L655Q; 555 | INFOPLIST_FILE = FaceApp/Info.plist; 556 | IPHONEOS_DEPLOYMENT_TARGET = 11.3; 557 | LD_RUNPATH_SEARCH_PATHS = ( 558 | "$(inherited)", 559 | "@executable_path/Frameworks", 560 | ); 561 | PRODUCT_BUNDLE_IDENTIFIER = c.arcobaleno.FaceApp; 562 | PRODUCT_NAME = "$(TARGET_NAME)"; 563 | SWIFT_VERSION = 4.0; 564 | TARGETED_DEVICE_FAMILY = "1,2"; 565 | }; 566 | name = Release; 567 | }; 568 | 2D77DC3C209F56680022BAAB /* Debug */ = { 569 | isa = XCBuildConfiguration; 570 | buildSettings = { 571 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 572 | BUNDLE_LOADER = "$(TEST_HOST)"; 573 | CODE_SIGN_STYLE = Automatic; 574 | INFOPLIST_FILE = FaceAppTests/Info.plist; 575 | LD_RUNPATH_SEARCH_PATHS = ( 576 | "$(inherited)", 577 | "@executable_path/Frameworks", 578 | "@loader_path/Frameworks", 579 | ); 580 | PRODUCT_BUNDLE_IDENTIFIER = c.arcobaleno.FaceAppTests; 581 | PRODUCT_NAME = "$(TARGET_NAME)"; 582 | SWIFT_VERSION = 4.0; 583 | TARGETED_DEVICE_FAMILY = "1,2"; 584 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FaceApp.app/FaceApp"; 585 | }; 586 | name = Debug; 587 | }; 588 | 2D77DC3D209F56680022BAAB /* Release */ = { 589 | isa = XCBuildConfiguration; 590 | buildSettings = { 591 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 592 | BUNDLE_LOADER = "$(TEST_HOST)"; 593 | CODE_SIGN_STYLE = Automatic; 594 | INFOPLIST_FILE = FaceAppTests/Info.plist; 595 | LD_RUNPATH_SEARCH_PATHS = ( 596 | "$(inherited)", 597 | "@executable_path/Frameworks", 598 | "@loader_path/Frameworks", 599 | ); 600 | PRODUCT_BUNDLE_IDENTIFIER = c.arcobaleno.FaceAppTests; 601 | PRODUCT_NAME = "$(TARGET_NAME)"; 602 | SWIFT_VERSION = 4.0; 603 | TARGETED_DEVICE_FAMILY = "1,2"; 604 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FaceApp.app/FaceApp"; 605 | }; 606 | name = Release; 607 | }; 608 | 2D77DC3F209F56680022BAAB /* Debug */ = { 609 | isa = XCBuildConfiguration; 610 | buildSettings = { 611 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 612 | CODE_SIGN_STYLE = Automatic; 613 | INFOPLIST_FILE = FaceAppUITests/Info.plist; 614 | LD_RUNPATH_SEARCH_PATHS = ( 615 | "$(inherited)", 616 | "@executable_path/Frameworks", 617 | "@loader_path/Frameworks", 618 | ); 619 | PRODUCT_BUNDLE_IDENTIFIER = c.arcobaleno.FaceAppUITests; 620 | PRODUCT_NAME = "$(TARGET_NAME)"; 621 | SWIFT_VERSION = 4.0; 622 | TARGETED_DEVICE_FAMILY = "1,2"; 623 | TEST_TARGET_NAME = FaceApp; 624 | }; 625 | name = Debug; 626 | }; 627 | 2D77DC40209F56680022BAAB /* Release */ = { 628 | isa = XCBuildConfiguration; 629 | buildSettings = { 630 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 631 | CODE_SIGN_STYLE = Automatic; 632 | INFOPLIST_FILE = FaceAppUITests/Info.plist; 633 | LD_RUNPATH_SEARCH_PATHS = ( 634 | "$(inherited)", 635 | "@executable_path/Frameworks", 636 | "@loader_path/Frameworks", 637 | ); 638 | PRODUCT_BUNDLE_IDENTIFIER = c.arcobaleno.FaceAppUITests; 639 | PRODUCT_NAME = "$(TARGET_NAME)"; 640 | SWIFT_VERSION = 4.0; 641 | TARGETED_DEVICE_FAMILY = "1,2"; 642 | TEST_TARGET_NAME = FaceApp; 643 | }; 644 | name = Release; 645 | }; 646 | /* End XCBuildConfiguration section */ 647 | 648 | /* Begin XCConfigurationList section */ 649 | 2D77DC0B209F56670022BAAB /* Build configuration list for PBXProject "FaceApp" */ = { 650 | isa = XCConfigurationList; 651 | buildConfigurations = ( 652 | 2D77DC36209F56680022BAAB /* Debug */, 653 | 2D77DC37209F56680022BAAB /* Release */, 654 | ); 655 | defaultConfigurationIsVisible = 0; 656 | defaultConfigurationName = Release; 657 | }; 658 | 2D77DC38209F56680022BAAB /* Build configuration list for PBXNativeTarget "FaceApp" */ = { 659 | isa = XCConfigurationList; 660 | buildConfigurations = ( 661 | 2D77DC39209F56680022BAAB /* Debug */, 662 | 2D77DC3A209F56680022BAAB /* Release */, 663 | ); 664 | defaultConfigurationIsVisible = 0; 665 | defaultConfigurationName = Release; 666 | }; 667 | 2D77DC3B209F56680022BAAB /* Build configuration list for PBXNativeTarget "FaceAppTests" */ = { 668 | isa = XCConfigurationList; 669 | buildConfigurations = ( 670 | 2D77DC3C209F56680022BAAB /* Debug */, 671 | 2D77DC3D209F56680022BAAB /* Release */, 672 | ); 673 | defaultConfigurationIsVisible = 0; 674 | defaultConfigurationName = Release; 675 | }; 676 | 2D77DC3E209F56680022BAAB /* Build configuration list for PBXNativeTarget "FaceAppUITests" */ = { 677 | isa = XCConfigurationList; 678 | buildConfigurations = ( 679 | 2D77DC3F209F56680022BAAB /* Debug */, 680 | 2D77DC40209F56680022BAAB /* Release */, 681 | ); 682 | defaultConfigurationIsVisible = 0; 683 | defaultConfigurationName = Release; 684 | }; 685 | /* End XCConfigurationList section */ 686 | }; 687 | rootObject = 2D77DC08209F56670022BAAB /* Project object */; 688 | } 689 | --------------------------------------------------------------------------------