├── readme-images ├── photoSelection.png └── photoIdentification.png ├── Podfile ├── ios-swift-faceapi-sample.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── project.pbxproj ├── ios-swift-faceapi-sample.xcworkspace ├── xcshareddata │ └── IDEWorkspaceChecks.plist └── contents.xcworkspacedata ├── ios-swift-faceapi-sample ├── ios-swift-faceapi-sample.entitlements ├── ViewController+Alert.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── AppDelegate.swift ├── ApplicationConstants.swift ├── Base.lproj │ └── LaunchScreen.storyboard ├── Info.plist ├── Graph.swift ├── SelectPersonTableViewController.swift ├── ConnectViewController.swift ├── AuthenticationProvider.swift ├── PhotoSelectorTableViewController.swift ├── FaceAPI.swift ├── FaceApiTableViewController.swift └── Main.storyboard ├── .gitignore ├── LICENSE ├── Readme.md └── CONTRIBUTING.md /readme-images/photoSelection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftgraph/ios-swift-faceapi-sample/master/readme-images/photoSelection.png -------------------------------------------------------------------------------- /readme-images/photoIdentification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftgraph/ios-swift-faceapi-sample/master/readme-images/photoIdentification.png -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '11.0' 2 | inhibit_all_warnings! 3 | use_frameworks! 4 | 5 | target 'ios-swift-faceapi-sample' do 6 | pod 'MSGraphSDK' 7 | pod 'MSAL', '~> 0.4.0' 8 | end 9 | -------------------------------------------------------------------------------- /ios-swift-faceapi-sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios-swift-faceapi-sample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios-swift-faceapi-sample.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios-swift-faceapi-sample/ios-swift-faceapi-sample.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | keychain-access-groups 6 | 7 | $(AppIdentifierPrefix)com.microsoft.adalcache 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios-swift-faceapi-sample/ViewController+Alert.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. 3 | * See LICENSE in the project root for license information. 4 | */ 5 | 6 | import UIKit 7 | 8 | extension UIViewController 9 | { 10 | func alert(title: String?, message: String?, buttonTitle: String = "Close") 11 | { 12 | DispatchQueue.main.async { 13 | let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) 14 | alert.addAction(UIAlertAction(title: buttonTitle, style: .default, handler: nil)) 15 | self.present(alert, animated: true, completion: nil) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | 3 | ## Build generated 4 | build/ 5 | DerivedData/ 6 | 7 | ## Various settings 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata/ 17 | 18 | ## Other 19 | *.moved-aside 20 | *.xcuserstate 21 | 22 | ## Obj-C/Swift specific 23 | *.hmap 24 | *.ipa 25 | *.dSYM.zip 26 | *.dSYM 27 | 28 | ## Playgrounds 29 | timeline.xctimeline 30 | playground.xcworkspace 31 | 32 | # Swift Package Manager 33 | # 34 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 35 | # Packages/ 36 | .build/ 37 | 38 | # CocoaPods 39 | # 40 | Pods/ 41 | *Podfile.lock 42 | -------------------------------------------------------------------------------- /ios-swift-faceapi-sample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /ios-swift-faceapi-sample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. 3 | * See LICENSE in the project root for license information. 4 | */ 5 | 6 | import UIKit 7 | import MSAL 8 | 9 | @UIApplicationMain 10 | class AppDelegate: UIResponder, UIApplicationDelegate 11 | { 12 | var window: UIWindow? 13 | 14 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool 15 | { 16 | return true 17 | } 18 | 19 | func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool 20 | { 21 | guard let sourceApplication = options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String else { return false } 22 | 23 | return MSALPublicClientApplication.handleMSALResponse(url, sourceApplication: sourceApplication) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /ios-swift-faceapi-sample/ApplicationConstants.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. 3 | * See LICENSE in the project root for license information. 4 | */ 5 | 6 | import Foundation 7 | 8 | struct ApplicationConstants 9 | { 10 | // Graph information 11 | static let clientId = "YOUR CLIENT ID" 12 | static let authority = "https://login.microsoftonline.com/common" 13 | static let scopes = ["User.ReadBasic.All"] 14 | 15 | // Cognitive services information 16 | static let ocpApimSubscriptionKey = "ENTER_SUBSCRIPTION_KEY" 17 | static let faceApiEndpoint = "https://eastus.api.cognitive.microsoft.com/face/v1.0" 18 | } 19 | 20 | enum ErrorType: Error 21 | { 22 | case UnexpectedError(nsError: NSError?) 23 | case ServiceError(json: [String: AnyObject]) 24 | case JSonSerializationError 25 | } 26 | 27 | typealias JSON = AnyObject 28 | typealias JSONDictionary = [String: JSON] 29 | typealias JSONArray = [JSON] 30 | 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Microsoft Corporation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /ios-swift-faceapi-sample/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 | -------------------------------------------------------------------------------- /ios-swift-faceapi-sample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | CFBundleURLTypes 40 | 41 | 42 | CFBundleTypeRole 43 | Editor 44 | CFBundleURLName 45 | $(PRODUCT_BUNDLE_IDENTIFIER) 46 | CFBundleURLSchemes 47 | 48 | msauth.com.microsoft.ios-swift-faceapi-sample 49 | 50 | 51 | 52 | LSApplicationQueriesSchemes 53 | 54 | msauth 55 | msauthv2 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /ios-swift-faceapi-sample/Graph.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. 3 | * See LICENSE in the project root for license information. 4 | */ 5 | 6 | import UIKit 7 | import MSGraphSDK 8 | 9 | enum GraphResult 10 | { 11 | case Success(T) 12 | case Failure(E) 13 | } 14 | 15 | struct Graph 16 | { 17 | var graphClient: MSGraphClient = { 18 | return MSGraphClient.defaultClient() 19 | }() 20 | 21 | // Read contacts 22 | func getUsers(with completion: @escaping (_ result: GraphResult<[MSGraphUser], Error>) -> Void) 23 | { 24 | graphClient.users().request().getWithCompletion { (userCollection, next, error) in 25 | 26 | if let nsError = error as NSError? { 27 | completion(.Failure(ErrorType.UnexpectedError(nsError: nsError))) 28 | } else { 29 | if let users = userCollection { 30 | completion(.Success(users.value as! [MSGraphUser])) 31 | } 32 | } 33 | } 34 | } 35 | 36 | // Get photovalue 37 | func getPhotoValue(forUser upn: String, with completion: @escaping (_ result: GraphResult) -> Void) 38 | { 39 | graphClient.users(upn).photoValue().download { (url, response, error) in 40 | 41 | if let nsError = error as NSError? { 42 | completion(.Failure(ErrorType.UnexpectedError(nsError: nsError))) 43 | return 44 | } 45 | 46 | guard let picUrl = url else { 47 | completion(.Failure(ErrorType.UnexpectedError(nsError: nil))) 48 | return 49 | } 50 | 51 | print(picUrl) 52 | 53 | let picData = NSData(contentsOf: picUrl) 54 | let picImage = UIImage(data: picData! as Data) 55 | 56 | do { 57 | try FileManager.default.removeItem(at: picUrl) 58 | } catch (let error) { 59 | print("delete error", error) 60 | } 61 | 62 | if let validPic = picImage { 63 | completion(.Success(validPic)) 64 | } else { 65 | completion(.Failure(ErrorType.UnexpectedError(nsError: nil))) 66 | } 67 | 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /ios-swift-faceapi-sample/SelectPersonTableViewController.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. 3 | * See LICENSE in the project root for license information. 4 | */ 5 | 6 | import UIKit 7 | 8 | class SelectPersonTableViewController: UITableViewController 9 | { 10 | var persons: [Person] = [Person]() 11 | var graph = Graph() 12 | var delegate: PersonSelecting! 13 | 14 | override func viewDidAppear(_ animated: Bool) 15 | { 16 | super.viewDidAppear(animated) 17 | 18 | loadDirectory() 19 | } 20 | 21 | // MARK: - Graph 22 | 23 | func loadDirectory() 24 | { 25 | persons.removeAll() 26 | 27 | graph.getUsers { (result) in 28 | switch (result) { 29 | case .Success(let result): 30 | let userCollection = result 31 | for user in userCollection { 32 | let person = Person(name: user.displayName, upn: user.userPrincipalName, image: nil) 33 | self.persons.append(person) 34 | } 35 | 36 | DispatchQueue.main.async { 37 | self.tableView.reloadData() 38 | } 39 | case .Failure(let error): 40 | print(error) 41 | } 42 | } 43 | } 44 | 45 | // MARK: - UITableView 46 | 47 | override func numberOfSections(in tableView: UITableView) -> Int { return 1 } 48 | 49 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 50 | { 51 | return persons.count 52 | } 53 | 54 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 55 | { 56 | let cell = tableView.dequeueReusableCell(withIdentifier: "basicCell", for: indexPath) 57 | cell.textLabel!.text = persons[indexPath.row].name 58 | return cell 59 | } 60 | 61 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 62 | { 63 | var person = persons[indexPath.row] 64 | graph.getPhotoValue(forUser: person.upn) { (result) in 65 | switch (result) { 66 | case .Success(let result): 67 | let image = result 68 | person.image = image 69 | self.delegate.select(person: person) 70 | 71 | DispatchQueue.main.async { 72 | self.navigationController?.popViewController(animated: true) 73 | } 74 | case .Failure(let error): 75 | print("Error", error) 76 | self.alert(title: "Error", message: "Check log for more details") 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /ios-swift-faceapi-sample/ConnectViewController.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. 3 | * See LICENSE in the project root for license information. 4 | */ 5 | 6 | import UIKit 7 | import MSAL 8 | import MSGraphSDK 9 | 10 | enum State 11 | { 12 | case Connecting 13 | case ReadyToConnect 14 | case Error 15 | } 16 | 17 | class ConnectViewController: UIViewController 18 | { 19 | @IBOutlet var connectButton: UIButton! 20 | var state: State = .ReadyToConnect 21 | let authenticationProvider: AuthenticationProvider? = { 22 | guard let authorityUrl = URL(string: ApplicationConstants.authority) else { return nil } 23 | 24 | var authenticationProvider: AuthenticationProvider? 25 | do { 26 | let authority = try MSALAADAuthority(url: authorityUrl) 27 | let clientId = ApplicationConstants.clientId 28 | authenticationProvider = try AuthenticationProvider(clientId: clientId, authority: authority) 29 | } catch let error as NSError { 30 | print("Error: ", error) 31 | } 32 | 33 | return authenticationProvider 34 | }() 35 | 36 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) 37 | { 38 | if segue.identifier == "selectPhoto" { 39 | let photoSelectorVC = segue.destination as! PhotoSelectorTableViewController 40 | photoSelectorVC.authenticationProvider = authenticationProvider 41 | } 42 | } 43 | 44 | // MARK:- IBAction 45 | 46 | @IBAction func connectToGraph(sender: AnyObject) 47 | { 48 | guard let authenticationProvider = self.authenticationProvider else { return } 49 | if state == .Connecting { return } 50 | 51 | setConnectButton(state: .Connecting) 52 | let scopes = ApplicationConstants.scopes 53 | 54 | authenticationProvider.acquireAuthToken(scopes: scopes) { (success, error) in 55 | if success { 56 | MSGraphClient.setAuthenticationProvider(self.authenticationProvider) 57 | self.setConnectButton(state: .ReadyToConnect) 58 | DispatchQueue.main.async { 59 | self.performSegue(withIdentifier: "selectPhoto", sender: nil) 60 | } 61 | 62 | return 63 | } 64 | 65 | print("[Error] ", error!) 66 | self.setConnectButton(state: .Error) 67 | } 68 | } 69 | 70 | // MARK:- Private 71 | 72 | private func setConnectButton(state: State) 73 | { 74 | DispatchQueue.main.async { 75 | switch state { 76 | case .Connecting: 77 | self.connectButton.isEnabled = false 78 | self.connectButton.setTitle("Connecting", for: .normal) 79 | case .ReadyToConnect: 80 | self.connectButton.isEnabled = true 81 | self.connectButton.setTitle("Start by connecting to Microsoft Graph", for: .normal) 82 | case .Error: 83 | self.connectButton.isEnabled = true 84 | self.connectButton.setTitle("Connection failed. Retry.", for: .normal) 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /ios-swift-faceapi-sample/AuthenticationProvider.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. 3 | * See LICENSE in the project root for license information. 4 | */ 5 | 6 | import Foundation 7 | 8 | import Foundation 9 | import MSAL 10 | import MSGraphSDK 11 | 12 | class AuthenticationProvider: NSObject, MSAuthenticationProvider 13 | { 14 | var accessToken: String? 15 | var account: MSALAccount? 16 | var msalClient: MSALPublicClientApplication 17 | 18 | init(clientId: String, authority: MSALAuthority) throws 19 | { 20 | let config = MSALPublicClientApplicationConfig(clientId: clientId) 21 | msalClient = try MSALPublicClientApplication(configuration: config) 22 | } 23 | 24 | func acquireAuthToken(scopes: [String], completion:@escaping (_ success:Bool, _ error: NSError?) -> Void) 25 | { 26 | var completionBlock: MSALCompletionBlock? 27 | completionBlock = { (result, error) in 28 | 29 | if let error = error as NSError? 30 | { 31 | if (error.domain == MSALErrorDomain) 32 | { 33 | let msalError = MSALError(rawValue: error.code)! 34 | 35 | switch msalError { 36 | case MSALError.interactionRequired: 37 | let interactiveParameters = MSALInteractiveTokenParameters(scopes: scopes) 38 | self.msalClient.acquireToken(with: interactiveParameters, completionBlock: completionBlock!) 39 | 40 | case MSALError.serverDeclinedScopes: 41 | var declinedScopes = [String]() 42 | if let ds = error.userInfo[MSALDeclinedScopesKey] as? Array { declinedScopes = ds } 43 | print("The following scopes were declined: ", declinedScopes) 44 | 45 | var grantedScopes = [String]() 46 | if let gs = error.userInfo[MSALGrantedScopesKey] as? Array { grantedScopes = gs } 47 | print("Trying to acquire a token with granted scopes: ", grantedScopes) 48 | 49 | self.acquireTokenSilent(scopes: grantedScopes, msalCompletionBlock: completionBlock!, completion: completion) 50 | 51 | case MSALError.internal: 52 | 53 | // Log the error, then inspect the MSALInternalErrorCodeKey 54 | // in the userInfo dictionary. 55 | // More detailed information about the specific error 56 | // under MSALInternalErrorCodeKey can be found in MSALInternalError enum. 57 | print("Failed with internal MSAL error ", error) 58 | default: 59 | print("Failed with unknown MSAL error ", error) 60 | } 61 | } 62 | 63 | completion(false, error) 64 | 65 | } else if let result = result { 66 | self.accessToken = result.accessToken 67 | self.account = result.account 68 | completion(true, nil) 69 | 70 | return 71 | } else { 72 | assert(false, "result and error should never be nil at the same time.") 73 | } 74 | } 75 | 76 | var accounts = [MSALAccount]() 77 | do { 78 | accounts = try msalClient.allAccounts() 79 | } catch let error as NSError { 80 | print("Error: ", error) 81 | } 82 | 83 | if let account = accounts.first { 84 | let silentParameters = MSALSilentTokenParameters(scopes: scopes, account: account) 85 | msalClient.acquireTokenSilent(with: silentParameters, completionBlock: completionBlock!) 86 | } else { 87 | let interactiveParameters = MSALInteractiveTokenParameters(scopes: scopes) 88 | msalClient.acquireToken(with: interactiveParameters, completionBlock: completionBlock!) 89 | } 90 | } 91 | 92 | func disconnect() 93 | { 94 | guard let account = self.account else { return } 95 | 96 | do { 97 | try msalClient.remove(account) 98 | } catch let error as NSError { 99 | print("Error: ", error) 100 | } 101 | } 102 | 103 | // MARK: - MSAuthenticationProvider 104 | 105 | func appendAuthenticationHeaders(_ request: NSMutableURLRequest!, completion completionHandler: MSAuthenticationCompletion!) 106 | { 107 | if let accessToken = self.accessToken { 108 | let header = "Bearer \(accessToken)" 109 | request.setValue(header, forHTTPHeaderField:"Authorization") 110 | } 111 | 112 | completionHandler(request, nil); 113 | } 114 | 115 | // MARK: - Private 116 | 117 | private func acquireTokenSilent(scopes: [String], msalCompletionBlock: @escaping MSALCompletionBlock, completion:@escaping (_ success:Bool, _ error: NSError?) -> Void) 118 | { 119 | var accounts = [MSALAccount]() 120 | do { 121 | accounts = try self.msalClient.allAccounts() 122 | } catch let error as NSError { 123 | print("Error: ", error) 124 | completion(false, error) 125 | return 126 | } 127 | 128 | if let account = accounts.first { 129 | let silentParameters = MSALSilentTokenParameters(scopes: scopes, account: account) 130 | self.msalClient.acquireTokenSilent(with: silentParameters, completionBlock: msalCompletionBlock) 131 | } else { 132 | completion(false, nil) 133 | } 134 | } 135 | } 136 | 137 | -------------------------------------------------------------------------------- /ios-swift-faceapi-sample/PhotoSelectorTableViewController.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. 3 | * See LICENSE in the project root for license information. 4 | */ 5 | 6 | import UIKit 7 | 8 | private enum CellIdentifier 9 | { 10 | static let selectPersonCell = "selectPersonCell" 11 | static let selectPhotoCell = "selectPhotoCell" 12 | static let photoCell = "photoCell" 13 | } 14 | 15 | private enum SelectedType 16 | { 17 | case singlePerson 18 | case photoForIdentification 19 | } 20 | 21 | struct Person 22 | { 23 | var name: String 24 | var upn: String 25 | var image: UIImage? 26 | } 27 | 28 | protocol PersonSelecting 29 | { 30 | func select(person: Person) 31 | } 32 | 33 | class PhotoSelectorTableViewController: UITableViewController, PersonSelecting, UIImagePickerControllerDelegate, UINavigationControllerDelegate 34 | { 35 | var authenticationProvider: AuthenticationProvider! 36 | var selectedPerson: Person? 37 | var selectedPhoto: UIImage? 38 | let imagePicker = UIImagePickerController() 39 | 40 | private var selectedType: SelectedType! 41 | 42 | @IBOutlet var startButton: UIBarButtonItem! 43 | @IBOutlet var backButton: UIBarButtonItem! 44 | 45 | override func viewDidLoad() 46 | { 47 | super.viewDidLoad() 48 | 49 | navigationItem.setHidesBackButton(true, animated: false) 50 | 51 | tableView.tableFooterView = UIView(frame: CGRect.zero) 52 | tableView.estimatedRowHeight = 44 53 | tableView.rowHeight = UITableView.automaticDimension 54 | 55 | imagePicker.delegate = self 56 | startButton.isEnabled = false 57 | } 58 | 59 | // MARK: - Navigation 60 | 61 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) 62 | { 63 | // Get the new view controller using segue.destinationViewController. 64 | // Pass the selected object to the new view controller. 65 | if segue.identifier == "selectPerson" { 66 | let selectPersonVC = segue.destination as! SelectPersonTableViewController 67 | selectPersonVC.delegate = self 68 | } 69 | 70 | else if segue.identifier == "startFaceAPI" { 71 | let faceAPIVC = segue.destination as! FaceApiTableViewController 72 | faceAPIVC.selectedPhoto = selectedPhoto 73 | faceAPIVC.selectedPerson = selectedPerson 74 | } 75 | } 76 | 77 | override func viewWillAppear(_ animated: Bool) 78 | { 79 | super.viewWillAppear(animated) 80 | 81 | if let _ = selectedPerson, 82 | let _ = selectedPhoto { 83 | startButton.isEnabled = true 84 | } 85 | else { 86 | startButton.isEnabled = false 87 | } 88 | } 89 | 90 | // MARK: - PersonSelecting, Photo selecting 91 | 92 | func select(person: Person) 93 | { 94 | selectedPerson = person 95 | DispatchQueue.main.async { 96 | self.tableView.reloadData() 97 | } 98 | } 99 | 100 | func selectPhoto() 101 | { 102 | imagePicker.allowsEditing = false 103 | imagePicker.sourceType = .photoLibrary 104 | 105 | present(imagePicker, animated: true, completion: nil) 106 | } 107 | 108 | func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) 109 | { 110 | if let pickedImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage { 111 | if selectedType == .singlePerson { 112 | let person = Person(name: "Local photo", upn: "local photo", image: pickedImage) 113 | selectedPerson = person 114 | } 115 | else { 116 | selectedPhoto = pickedImage 117 | } 118 | } 119 | 120 | dismiss(animated: true, completion: nil) 121 | 122 | tableView.reloadData() 123 | } 124 | 125 | func imagePickerControllerDidCancel(_ picker: UIImagePickerController) 126 | { 127 | dismiss(animated: true, completion: nil) 128 | } 129 | 130 | // MARK: - IBAction 131 | 132 | @IBAction func start(sender: AnyObject) 133 | { 134 | performSegue(withIdentifier: "startFaceAPI", sender: nil) 135 | } 136 | 137 | @IBAction func disconnect(sender: AnyObject) 138 | { 139 | authenticationProvider.disconnect() 140 | navigationController?.popViewController(animated: true) 141 | } 142 | 143 | // MARK: - UITableView 144 | 145 | override func numberOfSections(in tableView: UITableView) -> Int { return 1 } 146 | 147 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 148 | { 149 | if let _ = selectedPhoto { 150 | return 3 151 | } else { 152 | return 2 153 | } 154 | } 155 | 156 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 157 | { 158 | let cell: UITableViewCell 159 | 160 | if indexPath.row == 0 { 161 | cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier.selectPersonCell, for: indexPath) 162 | if let person = selectedPerson { 163 | cell.textLabel!.text = person.name 164 | cell.imageView!.image = person.image 165 | } 166 | } else if indexPath.row == 1 { 167 | cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier.selectPhotoCell, for: indexPath) 168 | if let _ = selectedPhoto { 169 | cell.accessoryType = .checkmark 170 | } else { 171 | cell.accessoryType = .none 172 | } 173 | } else { 174 | cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier.photoCell, for: indexPath) 175 | if let photo = selectedPhoto { 176 | let imageView = cell.viewWithTag(101) as! UIImageView 177 | imageView.image = photo 178 | } 179 | } 180 | 181 | return cell 182 | } 183 | 184 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 185 | { 186 | if indexPath.row == 0 { 187 | let alertController = UIAlertController(title: "Select source", message: "", preferredStyle: .actionSheet) 188 | alertController.addAction(UIAlertAction(title: "Graph Directory", style: .default, handler: { (action) in 189 | self.performSegue(withIdentifier: "selectPerson", sender: nil) 190 | })) 191 | alertController.addAction(UIAlertAction(title: "Photos", style: .default, handler: { (action) in 192 | self.selectedType = .singlePerson 193 | self.selectPhoto() 194 | })) 195 | alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) 196 | 197 | present(alertController, animated: true, completion: nil) 198 | } else if indexPath.row == 1 { 199 | self.selectedType = .photoForIdentification 200 | selectPhoto() 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Microsoft Cognitive Services with Graph SDK Sample for iOS 2 | 3 | This sample shows how to use both the [Microsoft Graph SDK for iOS](https://github.com/microsoftgraph/msgraph-sdk-ios) and the [Microsoft Cognitive Services Face API](https://www.microsoft.com/cognitive-services/en-us/face-api) in an iOS app. 4 | The user can select a photo locally from the device or from a user profile stored in Microsoft Exchange or Outlook. The sample uses the Face API to detect and identify the person in the photo. 5 | 6 | The sample code shows how to do the following: 7 | 8 | - Retrieve a user profile picture from the Office 365 directory of the signed-in user by using Microsoft Graph. 9 | - Create person group, add a person to the person group, train a person group, detect faces, and identify faces. 10 | 11 | ![PhotoSelection](/readme-images/photoSelection.png) ![PhotoIdentification](/readme-images/photoIdentification.png) 12 | 13 | ## Prerequisites 14 | 15 | - [Xcode](https://developer.apple.com/xcode/downloads/) version 10.2.1 16 | - Installation of [CocoaPods](https://guides.cocoapods.org/using/using-cocoapods.html) as a dependency manager. 17 | - A Microsoft work or school account. You can sign up for [an Office 365 Developer subscription](https://profile.microsoft.com/RegSysProfileCenter/wizardnp.aspx?wizid=14b845d0-938c-45af-b061-f798fbb4d170&lcid=1033) that includes the resources that you need to start building Office 365 apps. 18 | 19 | > Note: This sample relies on the user having an organizational account with a profile picture set in Microsoft Exchange or applied in the user's Outlook profile. If a profile picture is not present in either location, you'll see an error when the sample tries to retrieve the picture. 20 | 21 | - A subscription key for Cognitive Services. Check out services provided by [Microsoft Cognitive Services](https://www.microsoft.com/cognitive-services). Be sure to add a key for the Face API. You'll need to use that key value when you follow the steps in the **Running this sample in Xcode** section below. 22 | 23 | ## Register and configure the app 24 | 25 | 1. Open a browser and navigate to the [Azure Active Directory admin center](https://aad.portal.azure.com) and login using a **personal account** (aka: Microsoft Account) or **Work or School Account**. 26 | 27 | 1. Select **Azure Active Directory** in the left-hand navigation, then select **App registrations** under **Manage**. 28 | 29 | 1. Select **New registration**. On the **Register an application** page, set the values as follows. 30 | 31 | - Set **Name** to `Swift Face API Sample`. 32 | - Set **Supported account types** to **Accounts in any organizational directory and personal Microsoft accounts**. 33 | - Under **Redirect URI**, change the drop down to **Public client (mobile & desktop)**, and set the value to `msauth.com.microsoft.ios-swift-faceapi-sample://auth`. 34 | 35 | 1. Choose **Register**. On the **Swift Face API Sample** page, copy the value of the **Application (client) ID** and save it, you will need it in the next step. 36 | 37 | ## Running this sample in Xcode 38 | 39 | 1. Clone or download this repository. 40 | 1. Use CocoaPods to import the SDK dependencies. This sample app already contains a podfile that will get the pods into the project. Simply navigate to the project root from **Terminal** and run: 41 | 42 | ```Shell 43 | pod install 44 | ``` 45 | 46 | 1. Open **ios-swift-faceAPIs-with-Graph.xcworkspace**. 47 | 1. Open the **Application/ApplicationConstants.swift** file. Replace `YOUR CLIENT ID` with the application ID of your app registration. 48 | 1. Replace `ENTER_SUBSCRIPTION_KEY` with your Face API key. 49 | 1. Replace `YOUR_FACE_API_ENDPOINT` with the endpoint for your Face API cognitive service. See the [Azure documentation](https://docs.microsoft.com/azure/cognitive-services/face/quickstarts/curl#face-endpoint-url) for details. 50 | 1. Run the sample. You'll be asked to connect/authenticate to a work account and you'll need to provide your Office 365 credentials. Once authenticated you'll be taken to the photo selector controller to select a person to identify and a photo to identify from. 51 | 52 | ## Code of Interest 53 | 54 | ### Graph 55 | 56 | This sample contains two Microsoft Graph calls, both of which are in **Graph.swift** file under /Graph. 57 | 58 | 1. Get user's directory 59 | 60 | ```swift 61 | func getUsers(with completion: (result: GraphResult<[MSGraphUser], Error>) -> Void) { 62 | graphClient.users().request().getWithCompletion { 63 | (userCollection: MSCollection?, next: MSGraphUsersCollectionRequest?, error: NSError?) in 64 | ... 65 | } 66 | } 67 | } 68 | ``` 69 | 70 | 2. Get user profile (photo value) 71 | 72 | ```swift 73 | func getPhotoValue(forUser upn: String, with completion: (result: GraphResult) -> Void) { 74 | graphClient.users(upn).photoValue().downloadWithCompletion { 75 | (url: NSURL?, response: NSURLResponse?, error: NSError?) in 76 | ... 77 | } 78 | } 79 | ``` 80 | 81 | ### Cognitive Services - Face API 82 | 83 | This sample shows the basics of using the Microsoft Cognitive Services Face API to detect and identify faces. For more information, please visit [Microsoft Face API](https://www.microsoft.com/cognitive-services/en-us/face-api/documentation/overview) 84 | 85 | The code for identifying faces from scratch and related functions are in the **FaceAPI.swift** file under /CognitiveServices and the **FaceApiTableViewController.swift** file under /Controllers. 86 | 87 | These are the steps taken by the code: 88 | 89 | 1. Create person group. You can find the person group's name in the **FaceApiTableViewController.swift** file. 90 | 2. Create person in the new person group. 91 | 92 | (The person must be created within the group before uploading face(s) and training.) 93 | 3. Upload person's face. 94 | 4. Train person group. 95 | 5. Check & wait for the completion of training. 96 | 97 | This should take only a few seconds. Poll until it is complete and then proceed to the next step. 98 | 6. Detect faces. 99 | 7. Identify faces in the person group. 100 | 101 | > Note: This sample uses a single photo for training. In real-life scenarios, it would be advisable to use more than one photo for better accuracy. Also, this sample will create a person group as well as persons within that group. If you want to delete it, refer to [delete](https://dev.projectoxford.ai/docs/services/563879b61984550e40cbbe8d/operations/563879b61984550f30395245) api. 102 | 103 | ## Questions and comments 104 | 105 | We'd love to get your feedback about the Microsoft Graph SDK Profile Picture Sample. You can send your questions and suggestions to us in the [Issues](https://github.com/microsoftgraph/ios-swift-faceapi-sample/issues) section of this repository. 106 | 107 | Questions about Microsoft Graph development in general should be posted to [Stack Overflow](http://stackoverflow.com/questions/tagged/Office365+API). Make sure that your questions or comments are tagged with [Office365] and [MicrosoftGraph]. 108 | 109 | ## Contributing 110 | 111 | You will need to sign a [Contributor License Agreement](https://cla.microsoft.com/) before submitting your pull request. To complete the Contributor License Agreement (CLA), you will need to submit a request via the form and then electronically sign the CLA when you receive the email containing the link to the document. 112 | 113 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 114 | 115 | ## Copyright 116 | 117 | Copyright (c) 2016 Microsoft. All rights reserved. 118 | -------------------------------------------------------------------------------- /ios-swift-faceapi-sample/FaceAPI.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. 3 | * See LICENSE in the project root for license information. 4 | */ 5 | 6 | import UIKit 7 | 8 | enum FaceAPIResult 9 | { 10 | case Success(T) 11 | case Failure(E) 12 | } 13 | 14 | class FaceAPI: NSObject 15 | { 16 | // Create person group 17 | static func createPersonGroup(personGroupId: String, name: String, userData: String?, completion: @escaping (_ result: FaceAPIResult) -> Void) 18 | { 19 | let url = "\(ApplicationConstants.faceApiEndpoint)/persongroups/" 20 | let urlWithParams = url + personGroupId 21 | 22 | var request = URLRequest(url: URL(string: urlWithParams)!) 23 | request.httpMethod = "PUT" 24 | request.setValue("application/json", forHTTPHeaderField: "Content-Type") 25 | request.setValue(ApplicationConstants.ocpApimSubscriptionKey, forHTTPHeaderField: "Ocp-Apim-Subscription-Key") 26 | 27 | var json: [String: AnyObject] = ["name": name as AnyObject] 28 | 29 | if let userData = userData { 30 | json["userData"] = userData as AnyObject 31 | } 32 | 33 | let jsonData = try! JSONSerialization.data(withJSONObject: json, options: .prettyPrinted) 34 | request.httpBody = jsonData 35 | 36 | let task = URLSession.shared.dataTask(with: request) { (data, response, error) in 37 | 38 | if let nsError = error as NSError? { 39 | completion(.Failure(ErrorType.UnexpectedError(nsError: nsError))) 40 | } else { 41 | let httpResponse = response as! HTTPURLResponse 42 | let statusCode = httpResponse.statusCode 43 | 44 | if (statusCode == 200 || statusCode == 409) { 45 | completion(.Success([] as AnyObject)) 46 | } else { 47 | do { 48 | let json = try JSONSerialization.jsonObject(with: data!, options:.allowFragments) as! JSONDictionary 49 | completion(.Failure(ErrorType.ServiceError(json: json))) 50 | } catch { 51 | completion(.Failure(ErrorType.JSonSerializationError)) 52 | } 53 | } 54 | } 55 | } 56 | task.resume() 57 | } 58 | 59 | 60 | // Create person 61 | static func createPerson(personName: String, userData: String?, personGroupId: String, completion: @escaping (_ result: FaceAPIResult) -> Void) 62 | { 63 | let url = "\(ApplicationConstants.faceApiEndpoint)/persongroups/\(personGroupId)/persons" 64 | var request = URLRequest(url: URL(string: url)!) 65 | 66 | request.httpMethod = "POST" 67 | request.setValue("application/json", forHTTPHeaderField: "Content-Type") 68 | request.setValue(ApplicationConstants.ocpApimSubscriptionKey, forHTTPHeaderField: "Ocp-Apim-Subscription-Key") 69 | 70 | var json: [String: AnyObject] = ["name": personName as AnyObject] 71 | if let userData = userData { 72 | json["userData"] = userData as AnyObject 73 | } 74 | 75 | let jsonData = try! JSONSerialization.data(withJSONObject: json, options: .prettyPrinted) 76 | request.httpBody = jsonData 77 | 78 | let task = URLSession.shared.dataTask(with: request) { (data, response, error) in 79 | 80 | if let nsError = error as NSError? { 81 | completion(.Failure(ErrorType.UnexpectedError(nsError: nsError))) 82 | } else { 83 | let httpResponse = response as! HTTPURLResponse 84 | let statusCode = httpResponse.statusCode 85 | 86 | do { 87 | let json = try JSONSerialization.jsonObject(with: data!, options:.allowFragments) 88 | if statusCode == 200 { 89 | completion(.Success(json as AnyObject)) 90 | } 91 | } catch { 92 | completion(.Failure(ErrorType.JSonSerializationError)) 93 | } 94 | } 95 | } 96 | task.resume() 97 | } 98 | 99 | 100 | // Upload face 101 | static func uploadFace(faceImage: UIImage, personId: String, personGroupId: String, completion: @escaping (_ result: FaceAPIResult) -> Void) 102 | { 103 | let url = "\(ApplicationConstants.faceApiEndpoint)/persongroups/\(personGroupId)/persons/\(personId)/persistedFaces" 104 | var request = URLRequest(url: URL(string: url)!) 105 | 106 | request.httpMethod = "POST" 107 | request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type") 108 | request.setValue(ApplicationConstants.ocpApimSubscriptionKey, forHTTPHeaderField: "Ocp-Apim-Subscription-Key") 109 | 110 | let pngRepresentation = faceImage.pngData() 111 | 112 | let task = URLSession.shared.uploadTask(with: request, from: pngRepresentation) { (data, response, error) in 113 | 114 | if let nsError = error as NSError? { 115 | completion(.Failure(ErrorType.UnexpectedError(nsError: nsError))) 116 | } else { 117 | let httpResponse = response as! HTTPURLResponse 118 | let statusCode = httpResponse.statusCode 119 | 120 | do { 121 | let json = try JSONSerialization.jsonObject(with: data!, options:.allowFragments) 122 | if statusCode == 200 { 123 | completion(.Success(json as AnyObject)) 124 | } 125 | } catch { 126 | completion(.Failure(ErrorType.JSonSerializationError)) 127 | } 128 | } 129 | } 130 | task.resume() 131 | } 132 | 133 | 134 | // Post training 135 | static func trainPersonGroup(personGroupId: String, completion: @escaping (_ result: FaceAPIResult) -> Void) 136 | { 137 | let url = "\(ApplicationConstants.faceApiEndpoint)/persongroups/\(personGroupId)/train" 138 | var request = URLRequest(url: URL(string: url)!) 139 | 140 | request.httpMethod = "POST" 141 | request.setValue("application/json", forHTTPHeaderField: "Content-Type") 142 | request.setValue(ApplicationConstants.ocpApimSubscriptionKey, forHTTPHeaderField: "Ocp-Apim-Subscription-Key") 143 | 144 | let task = URLSession.shared.dataTask(with: request) { (data, response, error) in 145 | 146 | if let nsError = error as NSError? { 147 | completion(.Failure(ErrorType.UnexpectedError(nsError: nsError))) 148 | } else { 149 | let httpResponse = response as! HTTPURLResponse 150 | let statusCode = httpResponse.statusCode 151 | 152 | do { 153 | if statusCode == 202 { 154 | completion(.Success([] as AnyObject)) 155 | } else { 156 | let json = try JSONSerialization.jsonObject(with: data!, options:.allowFragments) as! JSONDictionary 157 | completion(.Failure(ErrorType.ServiceError(json: json))) 158 | } 159 | } catch { 160 | completion(.Failure(ErrorType.JSonSerializationError)) 161 | } 162 | } 163 | } 164 | task.resume() 165 | } 166 | 167 | 168 | // Get training status 169 | static func getTrainingStatus(personGroupId: String, completion: @escaping (_ result: FaceAPIResult) -> Void) 170 | { 171 | let url = "\(ApplicationConstants.faceApiEndpoint)/persongroups/\(personGroupId)/training" 172 | var request = URLRequest(url: URL(string: url)!) 173 | 174 | request.httpMethod = "GET" 175 | request.setValue(ApplicationConstants.ocpApimSubscriptionKey, forHTTPHeaderField: "Ocp-Apim-Subscription-Key") 176 | 177 | let task = URLSession.shared.dataTask(with: request) { (data, response, error) in 178 | 179 | if let nsError = error as NSError? { 180 | completion(.Failure(ErrorType.UnexpectedError(nsError: nsError))) 181 | } else { 182 | do { 183 | let json = try JSONSerialization.jsonObject(with: data!, options:.allowFragments) 184 | completion(.Success(json as AnyObject)) 185 | } catch { 186 | completion(.Failure(ErrorType.JSonSerializationError)) 187 | } 188 | } 189 | } 190 | task.resume() 191 | } 192 | 193 | 194 | // Detect faces 195 | static func detectFaces(facesPhoto: UIImage, completion: @escaping (_ result: FaceAPIResult) -> Void) 196 | { 197 | let url = "\(ApplicationConstants.faceApiEndpoint)/detect?returnFaceId=true&returnFaceLandmarks=false" 198 | var request = URLRequest(url: URL(string: url)!) 199 | 200 | request.httpMethod = "POST" 201 | request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type") 202 | request.setValue(ApplicationConstants.ocpApimSubscriptionKey, forHTTPHeaderField: "Ocp-Apim-Subscription-Key") 203 | 204 | let pngRepresentation = facesPhoto.pngData() 205 | 206 | let task = URLSession.shared.uploadTask(with: request, from: pngRepresentation) { (data, response, error) in 207 | 208 | if let nsError = error as NSError? { 209 | completion(.Failure(ErrorType.UnexpectedError(nsError: nsError))) 210 | } else { 211 | let httpResponse = response as! HTTPURLResponse 212 | let statusCode = httpResponse.statusCode 213 | 214 | do { 215 | let json = try JSONSerialization.jsonObject(with: data!, options:.allowFragments) 216 | if statusCode == 200 { 217 | completion(.Success(json as AnyObject)) 218 | } else { 219 | completion(.Failure(ErrorType.ServiceError(json: json as! [String : AnyObject]))) 220 | } 221 | } catch { 222 | completion(.Failure(ErrorType.JSonSerializationError)) 223 | } 224 | } 225 | } 226 | task.resume() 227 | } 228 | 229 | 230 | // Identify faces in people group 231 | static func identify(faces faceIds: [String], personGroupId: String, completion: @escaping (_ result: FaceAPIResult) -> Void) 232 | { 233 | let url = "\(ApplicationConstants.faceApiEndpoint)/identify" 234 | var request = URLRequest(url: URL(string: url)!) 235 | 236 | request.httpMethod = "POST" 237 | request.setValue("application/json", forHTTPHeaderField: "Content-Type") 238 | request.setValue(ApplicationConstants.ocpApimSubscriptionKey, forHTTPHeaderField: "Ocp-Apim-Subscription-Key") 239 | 240 | 241 | let json: [String: AnyObject] = ["personGroupId": personGroupId as AnyObject, 242 | "maxNumOfCandidatesReturned": 1 as AnyObject, 243 | "confidenceThreshold": 0.7 as AnyObject, 244 | "faceIds": faceIds as AnyObject 245 | ] 246 | 247 | let jsonData = try! JSONSerialization.data(withJSONObject: json, options: .prettyPrinted) 248 | request.httpBody = jsonData 249 | 250 | let task = URLSession.shared.dataTask(with: request) { (data, response, error) in 251 | 252 | if let nsError = error as NSError? { 253 | completion(.Failure(ErrorType.UnexpectedError(nsError: nsError))) 254 | } else { 255 | let httpResponse = response as! HTTPURLResponse 256 | let statusCode = httpResponse.statusCode 257 | 258 | do { 259 | let json = try JSONSerialization.jsonObject(with: data!, options:.allowFragments) 260 | if statusCode == 200 { 261 | completion(.Success(json as AnyObject)) 262 | } else { 263 | completion(.Failure(ErrorType.ServiceError(json: json as! JSONDictionary))) 264 | } 265 | } catch { 266 | completion(.Failure(ErrorType.JSonSerializationError)) 267 | } 268 | } 269 | } 270 | task.resume() 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contribute to this documentation 2 | 3 | Thank you for your interest in our documentation! 4 | 5 | * [Ways to contribute](#ways-to-contribute) 6 | * [Contribute using GitHub](#contribute-using-github) 7 | * [Contribute using Git](#contribute-using-git) 8 | * [How to use Markdown to format your topic](#how-to-use-markdown-to-format-your-topic) 9 | * [FAQ](#faq) 10 | * [More resources](#more-resources) 11 | 12 | ## Ways to contribute 13 | 14 | Here are some ways you can contribute to this documentation: 15 | 16 | * To make small changes to an article, [Contribute using GitHub](#contribute-using-github). 17 | * To make large changes, or changes that involve code, [Contribute using Git](#contribute-using-git). 18 | * Report documentation bugs via GitHub Issues 19 | * Request new documentation at the [Office Developer Platform UserVoice](http://officespdev.uservoice.com) site. 20 | 21 | ## Contribute using GitHub 22 | 23 | Use GitHub to contribute to this documentation without having to clone the repo to your desktop. This is the easiest way to create a pull request in this repository. Use this method to make a minor change that doesn't involve code changes. 24 | 25 | **Note** Using this method allows you to contribute to one article at a time. 26 | 27 | ### To Contribute using GitHub 28 | 29 | 1. Find the article you want to contribute to on GitHub. 30 | 31 | If the article is in MSDN, choose the **suggest and submit changes** link in the **Contribute to this content** section and you'll be taken to the same article on GitHub. 32 | 2. Once you are on the article in GitHub, sign in to GitHub (get a free account [Join GitHub](https://github.com/join). 33 | 3. Choose the **pencil icon** (edit the file in your fork of this project) and make your changes in the **<>Edit file** window. 34 | 4. Scroll to the bottom and enter a description. 35 | 5. Choose **Propose file change**>**Create pull request**. 36 | 37 | You now have successfully submitted a pull request. Pull requests are typically reviewed within 10 business days. 38 | 39 | 40 | ## Contribute using Git 41 | 42 | Use Git to contribute substantive changes, such as: 43 | 44 | * Contributing code. 45 | * Contributing changes that affect meaning. 46 | * Contributing large changes to text. 47 | * Adding new topics. 48 | 49 | ### To Contribute using Git 50 | 51 | 1. If you don't have a GitHub account, set one up at [GitHub](https://github.com/join). 52 | 2. After you have an account, install Git on your computer. Follow the steps in [Setting up Git Tutorial](https://help.github.com/articles/set-up-git/). 53 | 3. To submit a pull request using Git, follow the steps in [Use GitHub, Git, and this repository](#use-github-git-and-this-repository). 54 | 4. You will be asked to sign the Contributor's License Agreement if you are: 55 | 56 | * A member of the Microsoft Open Technologies group. 57 | * A contributors who doesn't work for Microsoft. 58 | 59 | As a community member, you must sign the Contribution License Agreement (CLA) before you can contribute large submissions to a project. You only need to complete and submit the documentation once. Carefully review the document. You may be required to have your employer sign the document. 60 | 61 | Signing the CLA does not grant you rights to commit to the main repository, but it does mean that the Office Developer and Office Developer Content Publishing teams will be able to review and approve your contributions. You will be credited for your submissions. 62 | 63 | Pull requests are typically reviewed within 10 business days. 64 | 65 | ## Use GitHub, Git, and this repository 66 | 67 | **Note:** Most of the information in this section can be found in [GitHub Help] articles. If you're familiar with Git and GitHub, skip to the **Contribute and edit content** section for the specifics of the code/content flow of this repository. 68 | 69 | ### To set up your fork of the repository 70 | 71 | 1. Set up a GitHub account so you can contribute to this project. If you haven't done this, go to [GitHub](https://github.com/join) and do it now. 72 | 2. Install Git on your computer. Follow the steps in the [Setting up Git Tutorial] [Set Up Git]. 73 | 3. Create your own fork of this repository. To do this, at the top of the page, choose the **Fork** button. 74 | 4. Copy your fork to your computer. To do this, open Git Bash. At the command prompt enter: 75 | 76 | git clone https://github.com//.git 77 | 78 | Next, create a reference to the root repository by entering these commands: 79 | 80 | cd 81 | git remote add upstream https://github.com/microsoftgraph/.git 82 | git fetch upstream 83 | 84 | Congratulations! You've now set up your repository. You won't need to repeat these steps again. 85 | 86 | ### Contribute and edit content 87 | 88 | To make the contribution process as seamless as possible, follow these steps. 89 | 90 | #### To contribute and edit content 91 | 92 | 1. Create a new branch. 93 | 2. Add new content or edit existing content. 94 | 3. Submit a pull request to the main repository. 95 | 4. Delete the branch. 96 | 97 | **Important** Limit each branch to a single concept/article to streamline the work flow and reduce the chance of merge conflicts. Content appropriate for a new branch includes: 98 | 99 | * A new article. 100 | * Spelling and grammar edits. 101 | * Applying a single formatting change across a large set of articles (for example, applying a new copyright footer). 102 | 103 | #### To create a new branch 104 | 105 | 1. Open Git Bash. 106 | 2. At the Git Bash command prompt, type `git pull upstream master:`. This creates a new branch locally that is copied from the latest MicrosoftGraph master branch. 107 | 3. At the Git Bash command prompt, type `git push origin `. This alerts GitHub to the new branch. You should now see the new branch in your fork of the repository on GitHub. 108 | 4. At the Git Bash command prompt, type `git checkout ` to switch to your new branch. 109 | 110 | #### Add new content or edit existing content 111 | 112 | You navigate to the repository on your computer by using File Explorer. The repository files are in `C:\Users\\`. 113 | 114 | To edit files, open them in an editor of your choice and modify them. To create a new file, use the editor of your choice and save the new file in the appropriate location in your local copy of the repository. While working, save your work frequently. 115 | 116 | The files in `C:\Users\\` are a working copy of the new branch that you created in your local repository. Changing anything in this folder doesn't affect the local repository until you commit a change. To commit a change to the local repository, type the following commands in GitBash: 117 | 118 | git add . 119 | git commit -v -a -m "" 120 | 121 | The `add` command adds your changes to a staging area in preparation for committing them to the repository. The period after the `add` command specifies that you want to stage all of the files that you added or modified, checking subfolders recursively. (If you don't want to commit all of the changes, you can add specific files. You can also undo a commit. For help, type `git add -help` or `git status`.) 122 | 123 | The `commit` command applies the staged changes to the repository. The switch `-m` means you are providing the commit comment in the command line. The -v and -a switches can be omitted. The -v switch is for verbose output from the command, and -a does what you already did with the add command. 124 | 125 | You can commit multiple times while you are doing your work, or you can commit once when you're done. 126 | 127 | #### Submit a pull request to the main repository 128 | 129 | When you're finished with your work and are ready to have it merged into the main repository, follow these steps. 130 | 131 | #### To submit a pull request to the main repository 132 | 133 | 1. In the Git Bash command prompt, type `git push origin `. In your local repository, `origin` refers to your GitHub repository that you cloned the local repository from. This command pushes the current state of your new branch, including all commits made in the previous steps, to your GitHub fork. 134 | 2. On the GitHub site, navigate in your fork to the new branch. 135 | 3. Choose the **Pull Request** button at the top of the page. 136 | 4. Verify the Base branch is `microsoftgraph/@master` and the Head branch is `/@`. 137 | 5. Choose the **Update Commit Range** button. 138 | 6. Add a title to your pull request, and describe all the changes you're making. 139 | 7. Submit the pull request. 140 | 141 | One of the site administrators will process your pull request. Your pull request will surface on the microsoftgraph/ site under Issues. When the pull request is accepted, the issue will be resolved. 142 | 143 | #### Create a new branch after merge 144 | 145 | After a branch is successfully merged (that is, your pull request is accepted), don't continue working in that local branch. This can lead to merge conflicts if you submit another pull request. To do another update, create a new local branch from the successfully merged upstream branch, and then delete your initial local branch. 146 | 147 | For example, if your local branch X was successfully merged into the OfficeDev/microsoft-graph-docs master branch and you want to make additional updates to the content that was merged. Create a new local branch, X2, from the OfficeDev/microsoft-graph-docs master branch. To do this, open GitBash and execute the following commands: 148 | 149 | cd microsoft-graph-docs 150 | git pull upstream master:X2 151 | git push origin X2 152 | 153 | You now have local copies (in a new local branch) of the work that you submitted in branch X. The X2 branch also contains all the work other writers have merged, so if your work depends on others' work (for example, shared images), it is available in the new branch. You can verify that your previous work (and others' work) is in the branch by checking out the new branch... 154 | 155 | git checkout X2 156 | 157 | ...and verifying the content. (The `checkout` command updates the files in `C:\Users\\microsoft-graph-docs` to the current state of the X2 branch.) Once you check out the new branch, you can make updates to the content and commit them as usual. However, to avoid working in the merged branch (X) by mistake, it's best to delete it (see the following **Delete a branch** section). 158 | 159 | #### Delete a branch 160 | 161 | Once your changes are successfully merged into the main repository, delete the branch you used because you no longer need it. Any additional work should be done in a new branch. 162 | 163 | #### To delete a branch 164 | 165 | 1. In the Git Bash command prompt, type `git checkout master`. This ensures that you aren't in the branch to be deleted (which isn't allowed). 166 | 2. Next, at the command prompt, type `git branch -d `. This deletes the branch on your computer only if it has been successfully merged to the upstream repository. (You can override this behavior with the `–D` flag, but first be sure you want to do this.) 167 | 3. Finally, type `git push origin :` at the command prompt (a space before the colon and no space after it). This will delete the branch on your github fork. 168 | 169 | Congratulations, you have successfully contributed to the project! 170 | 171 | ## How to use Markdown to format your topic 172 | 173 | ### Article template 174 | 175 | The [markdown template](/articles/0-markdown-template-for-new-articles.md) contains the basic Markdown for a topic that includes a table of contents, sections with subheadings, links to other Office developer topics, links to other sites, bold text, italic text, numbered and bulleted lists, code snippets, and images. 176 | 177 | 178 | ### Standard Markdown 179 | 180 | All of the articles in this repository use Markdown. A complete introduction (and listing of all the syntax) can be found at [Markdown Home] []. 181 | 182 | ## FAQ 183 | 184 | ### How do I get a GitHub account? 185 | 186 | Fill out the form at [Join GitHub](https://github.com/join) to open a free GitHub account. 187 | 188 | ### Where do I get a Contributor's License Agreement? 189 | 190 | You will automatically be sent a notice that you need to sign the Contributor's License Agreement (CLA) if your pull request requires one. 191 | 192 | As a community member, **you must sign the Contribution License Agreement (CLA) before you can contribute large submissions to this project**. You only need complete and submit the documentation once. Carefully review the document. You may be required to have your employer sign the document. 193 | 194 | ### What happens with my contributions? 195 | 196 | When you submit your changes, via a pull request, our team will be notified and will review your pull request. You will receive notifications about your pull request from GitHub; you may also be notified by someone from our team if we need more information. We reserve the right to edit your submission for legal, style, clarity, or other issues. 197 | 198 | ### Can I become an approver for this repository's GitHub pull requests? 199 | 200 | Currently, we are not allowing external contributors to approve pull requests in this repository. 201 | 202 | ### How soon will I get a response about my change request or issue? 203 | 204 | We typically review pull requests and respond to issues within 10 business days. 205 | 206 | ## More resources 207 | 208 | * To learn more about Markdown, go to the Git creator's site [Daring Fireball]. 209 | * To learn more about using Git and GitHub, first check out the [GitHub Help section] [GitHub Help]. 210 | 211 | [GitHub Home]: http://github.com 212 | [GitHub Help]: http://help.github.com/ 213 | [Set Up Git]: http://help.github.com/win-set-up-git/ 214 | [Markdown Home]: http://daringfireball.net/projects/markdown/ 215 | [Daring Fireball]: http://daringfireball.net/ 216 | -------------------------------------------------------------------------------- /ios-swift-faceapi-sample/FaceApiTableViewController.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. 3 | * See LICENSE in the project root for license information. 4 | */ 5 | 6 | import UIKit 7 | 8 | private enum CellIdentifier 9 | { 10 | static let loadingCell = "loadingCell" 11 | static let photoCell = "photoCell" 12 | static let resultCell = "resultCell" 13 | static let notFoundCell = "notFoundCell" 14 | } 15 | 16 | struct Result 17 | { 18 | let image: UIImage 19 | let otherInformation: String 20 | } 21 | 22 | struct Face 23 | { 24 | let faceId: String 25 | let height: Int 26 | let width: Int 27 | let top: Int 28 | let left: Int 29 | } 30 | 31 | private enum FaceAPIConstant 32 | { 33 | static let personGroupId = "sample-person-group-using-graph" 34 | } 35 | 36 | class FaceApiTableViewController: UITableViewController 37 | { 38 | var selectedPerson: Person! 39 | var selectedPhoto: UIImage! 40 | var isLoading: Bool = true 41 | var result = [Result]() 42 | 43 | override func viewDidLoad() 44 | { 45 | super.viewDidLoad() 46 | 47 | tableView.tableFooterView = UIView(frame: CGRect.zero) 48 | tableView.estimatedRowHeight = 44 49 | tableView.rowHeight = UITableView.automaticDimension 50 | } 51 | 52 | override func viewDidAppear(_ animated: Bool) 53 | { 54 | super.viewDidAppear(animated) 55 | identifyFace() 56 | } 57 | } 58 | 59 | 60 | // MARK: - TableView 61 | extension FaceApiTableViewController 62 | { 63 | 64 | override func numberOfSections(in tableView: UITableView) -> Int { return 1 } 65 | 66 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 67 | { 68 | if isLoading { 69 | return 2 70 | } else { 71 | return 1 + max(result.count, 1) 72 | } 73 | } 74 | 75 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 76 | { 77 | if indexPath.row == 0 { 78 | let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier.photoCell, for: indexPath) 79 | let imageView = cell.viewWithTag(101) as! UIImageView 80 | imageView.image = selectedPhoto 81 | return cell 82 | } else if indexPath.row == 1 && isLoading == true { 83 | return tableView.dequeueReusableCell(withIdentifier: CellIdentifier.loadingCell, for: indexPath) 84 | } else { 85 | if result.count - 1 < indexPath.row - 1 { 86 | return tableView.dequeueReusableCell(withIdentifier: CellIdentifier.notFoundCell, for: indexPath) 87 | } 88 | 89 | let record = result[indexPath.row - 1] 90 | 91 | let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier.resultCell, for: indexPath) 92 | let imageView = cell.viewWithTag(101) as! UIImageView 93 | let label = cell.viewWithTag(102) as! UILabel 94 | 95 | imageView.image = record.image 96 | label.text = record.otherInformation 97 | 98 | return cell 99 | } 100 | } 101 | } 102 | 103 | 104 | // MARK: - Face API 105 | extension FaceApiTableViewController 106 | { 107 | 108 | // In order for detection to work, 109 | // We need to: 110 | // 1. create personGroup : 111 | // 2. add a person to personGroup 112 | // 3. upload person's face(s) 113 | // 4. train 114 | // 5. check train completion 115 | // 5. detect faces in a photo 116 | // 6. identify 117 | 118 | func identifyFace() 119 | { 120 | createPersonGroup(groupId: FaceAPIConstant.personGroupId) 121 | } 122 | 123 | func createPersonGroup(groupId: String) { 124 | FaceAPI.createPersonGroup(personGroupId: groupId, 125 | name: "SampleGroup", 126 | userData: "This is a sample group") { (result) in 127 | switch result { 128 | case .Success(let json): 129 | print("Created person group - ", json) 130 | self.addPerson(name: self.selectedPerson.name, userData: nil, personGroupId: groupId) 131 | case .Failure(let error): 132 | print("Error creating person group - ", error) 133 | self.alert(title: "Error", message: "Check log for more details") 134 | } 135 | } 136 | } 137 | 138 | func addPerson(name: String, userData: String?, personGroupId: String) 139 | { 140 | FaceAPI.createPerson(personName: name, userData: userData, personGroupId: personGroupId) { (result) in 141 | switch result { 142 | case .Success(let json): 143 | 144 | let personId = json["personId"] as! String 145 | print("Created person - ", personId) 146 | self.uploadPersonFace(image: self.selectedPerson.image!, personId: personId, personGroupId: personGroupId) 147 | break 148 | case .Failure(let error): 149 | print("Error adding a person - ", error) 150 | self.alert(title: "Error", message: "Check log for more details") 151 | self.isLoading = false 152 | DispatchQueue.main.async { 153 | self.tableView.reloadData() 154 | } 155 | 156 | break 157 | } 158 | } 159 | } 160 | 161 | 162 | func uploadPersonFace(image: UIImage, personId: String, personGroupId: String) 163 | { 164 | FaceAPI.uploadFace(faceImage: image, personId: personId, personGroupId: personGroupId) { (result) in 165 | switch result { 166 | case .Success(_): 167 | print("face uploaded - ", personId) 168 | self.train(personGroupId: personGroupId, personToFind: personId) 169 | break 170 | case .Failure(let error): 171 | print("Error uploading a face - ", error) 172 | self.alert(title: "Error", message: "Check log for more details") 173 | self.isLoading = false 174 | DispatchQueue.main.async { 175 | self.tableView.reloadData() 176 | } 177 | break 178 | } 179 | } 180 | } 181 | 182 | func train(personGroupId: String, personToFind: String) 183 | { 184 | FaceAPI.trainPersonGroup(personGroupId: personGroupId) { (result) in 185 | switch result { 186 | case .Success(_): 187 | print("train posted") 188 | self.checkForTrainComplete(personGroupId: personGroupId, completion: { 189 | self.detectFaces(photo: self.selectedPhoto, completion: { (faces) in 190 | self.identifyFaces(faces: faces, personGroupId: personGroupId, personToFind: personToFind) 191 | }) 192 | }) 193 | break 194 | case .Failure(let error): 195 | print("Error posting to train - ", error) 196 | self.alert(title: "Error", message: "Check log for more details") 197 | self.isLoading = false 198 | DispatchQueue.main.async { 199 | self.tableView.reloadData() 200 | } 201 | break 202 | } 203 | } 204 | } 205 | 206 | 207 | func checkForTrainComplete(personGroupId: String, completion: @escaping () -> Void) { 208 | FaceAPI.getTrainingStatus(personGroupId: personGroupId) { (result) in 209 | switch result { 210 | case .Success(let json): 211 | print("training complete - ", json) 212 | let status = json["status"] as! String 213 | 214 | if status == "notstarted" || status == "running" { 215 | print("Training in progress") 216 | 217 | let delayTime = DispatchTime.now() + .seconds(1) 218 | DispatchQueue.main.asyncAfter(deadline: delayTime) { 219 | self.checkForTrainComplete(personGroupId: personGroupId, completion: completion) 220 | } 221 | } 222 | else if status == "failed" { 223 | print("Training failed -", json) 224 | self.alert(title: "Error", message: "Check log for more details") 225 | } 226 | else if status == "succeeded" { 227 | print("Training succeeded") 228 | completion() 229 | } 230 | 231 | break 232 | case .Failure(let error): 233 | print("Training incomplete or error - ", error) 234 | self.alert(title: "Error", message: "Check log for more details") 235 | self.isLoading = false 236 | DispatchQueue.main.async { 237 | self.tableView.reloadData() 238 | } 239 | break 240 | } 241 | } 242 | } 243 | 244 | func detectFaces(photo: UIImage, completion: @escaping (_ faces: [Face]) -> Void) 245 | { 246 | FaceAPI.detectFaces(facesPhoto: photo) { (result) in 247 | switch result { 248 | case .Success(let json): 249 | var faces = [Face]() 250 | 251 | let detectedFaces = json as! JSONArray 252 | for item in detectedFaces { 253 | let face = item as! JSONDictionary 254 | let faceId = face["faceId"] as! String 255 | let rectangle = face["faceRectangle"] as! [String: AnyObject] 256 | 257 | let detectedFace = Face(faceId: faceId, 258 | height: rectangle["top"] as! Int, 259 | width: rectangle["width"] as! Int, 260 | top: rectangle["top"] as! Int, 261 | left: rectangle["left"] as! Int) 262 | faces.append(detectedFace) 263 | } 264 | completion(faces) 265 | break 266 | case .Failure(let error): 267 | print("DetectFaces error - ", error) 268 | self.alert(title: "Error", message: "Check log for more details") 269 | self.isLoading = false 270 | DispatchQueue.main.async { 271 | self.tableView.reloadData() 272 | } 273 | 274 | break 275 | } 276 | } 277 | } 278 | 279 | func identifyFaces(faces: [Face], personGroupId: String, personToFind: String) { 280 | 281 | print("Looking for", personToFind) 282 | print("in group", personGroupId) 283 | var faceIds = [String]() 284 | for face in faces { 285 | faceIds.append(face.faceId) 286 | } 287 | 288 | FaceAPI.identify(faces: faceIds, personGroupId: personGroupId) { (result) in 289 | switch result { 290 | case .Success(let json): 291 | let jsonArray = json as! JSONArray 292 | 293 | for item in jsonArray { 294 | let face = item as! JSONDictionary 295 | 296 | let faceId = face["faceId"] as! String 297 | let candidates = face["candidates"] as! JSONArray 298 | 299 | for candidate in candidates { 300 | 301 | if candidate["personId"] as! String == personToFind { 302 | // find face information based on faceId 303 | for face in faces { 304 | if face.faceId == faceId { 305 | let faceImage = self.cropFace(face: face, image: self.selectedPhoto) 306 | let confidence = candidate["confidence"] as! CFNumber 307 | 308 | var outputString = "confidence: \(confidence)\n" 309 | outputString += "dimensions: \n"; 310 | outputString += " top : \(Int(face.top))\n" 311 | outputString += " left : \(Int(face.left))\n" 312 | outputString += " width : \(Int(face.width))\n" 313 | outputString += " height: \(Int(face.height))\n" 314 | 315 | let detectedFace = Result(image: faceImage, otherInformation: outputString) 316 | self.result.append(detectedFace) 317 | } 318 | } 319 | } 320 | } 321 | 322 | self.isLoading = false 323 | DispatchQueue.main.async { 324 | self.tableView.reloadData() 325 | } 326 | } 327 | case .Failure(let error): 328 | print("Identifying faces error - ", error) 329 | self.alert(title: "Error", message: "Check log for more details") 330 | self.isLoading = false 331 | DispatchQueue.main.async { 332 | self.tableView.reloadData() 333 | } 334 | break 335 | } 336 | } 337 | } 338 | 339 | 340 | func cropFace(face: Face, image: UIImage) -> UIImage 341 | { 342 | let croppedSection = CGRect(x: CGFloat(face.left), y: CGFloat(face.top), width: CGFloat(face.width), height: CGFloat(face.height)) 343 | let imageRef = image.cgImage!.cropping(to: croppedSection) 344 | 345 | let croppedImage = UIImage(cgImage: imageRef!) 346 | 347 | return croppedImage 348 | } 349 | 350 | } 351 | -------------------------------------------------------------------------------- /ios-swift-faceapi-sample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 966013A51D81DAA60066DB1D /* ApplicationConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 966013A41D81DAA60066DB1D /* ApplicationConstants.swift */; }; 11 | 966013A71D81DABA0066DB1D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 966013A61D81DABA0066DB1D /* Main.storyboard */; }; 12 | 966013AD1D81DAF60066DB1D /* ViewController+Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 966013A81D81DAF60066DB1D /* ViewController+Alert.swift */; }; 13 | 966013AE1D81DAF60066DB1D /* ConnectViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 966013A91D81DAF60066DB1D /* ConnectViewController.swift */; }; 14 | 966013AF1D81DAF60066DB1D /* PhotoSelectorTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 966013AA1D81DAF60066DB1D /* PhotoSelectorTableViewController.swift */; }; 15 | 966013B01D81DAF60066DB1D /* SelectPersonTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 966013AB1D81DAF60066DB1D /* SelectPersonTableViewController.swift */; }; 16 | 966013B11D81DAF60066DB1D /* FaceApiTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 966013AC1D81DAF60066DB1D /* FaceApiTableViewController.swift */; }; 17 | 966013B41D81DAFB0066DB1D /* AuthenticationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 966013B21D81DAFB0066DB1D /* AuthenticationProvider.swift */; }; 18 | 966013B51D81DAFB0066DB1D /* Graph.swift in Sources */ = {isa = PBXBuildFile; fileRef = 966013B31D81DAFB0066DB1D /* Graph.swift */; }; 19 | 966013B71D81DAFF0066DB1D /* FaceAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 966013B61D81DAFF0066DB1D /* FaceAPI.swift */; }; 20 | 966013B91D81DB280066DB1D /* Readme.md in Sources */ = {isa = PBXBuildFile; fileRef = 966013B81D81DB280066DB1D /* Readme.md */; }; 21 | 96A8C0E11D81D9F100797080 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96A8C0E01D81D9F100797080 /* AppDelegate.swift */; }; 22 | 96A8C0E81D81D9F100797080 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 96A8C0E71D81D9F100797080 /* Assets.xcassets */; }; 23 | 96A8C0EB1D81D9F100797080 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 96A8C0E91D81D9F100797080 /* LaunchScreen.storyboard */; }; 24 | EDD8813B37568D83E0973C98 /* Pods_ios_swift_faceapi_sample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AABC9B24BB9CA0FB45F203BD /* Pods_ios_swift_faceapi_sample.framework */; }; 25 | /* End PBXBuildFile section */ 26 | 27 | /* Begin PBXFileReference section */ 28 | 238BA008227BB14A00A5BACD /* ios-swift-faceapi-sample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "ios-swift-faceapi-sample.entitlements"; sourceTree = ""; }; 29 | 3215642D1D267668AD70ABAD /* Pods-ios-swift-faceapi-sample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ios-swift-faceapi-sample.release.xcconfig"; path = "Pods/Target Support Files/Pods-ios-swift-faceapi-sample/Pods-ios-swift-faceapi-sample.release.xcconfig"; sourceTree = ""; }; 30 | 966013A41D81DAA60066DB1D /* ApplicationConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplicationConstants.swift; sourceTree = ""; }; 31 | 966013A61D81DABA0066DB1D /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 32 | 966013A81D81DAF60066DB1D /* ViewController+Alert.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ViewController+Alert.swift"; sourceTree = ""; }; 33 | 966013A91D81DAF60066DB1D /* ConnectViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectViewController.swift; sourceTree = ""; }; 34 | 966013AA1D81DAF60066DB1D /* PhotoSelectorTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoSelectorTableViewController.swift; sourceTree = ""; }; 35 | 966013AB1D81DAF60066DB1D /* SelectPersonTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectPersonTableViewController.swift; sourceTree = ""; }; 36 | 966013AC1D81DAF60066DB1D /* FaceApiTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FaceApiTableViewController.swift; sourceTree = ""; }; 37 | 966013B21D81DAFB0066DB1D /* AuthenticationProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticationProvider.swift; sourceTree = ""; }; 38 | 966013B31D81DAFB0066DB1D /* Graph.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Graph.swift; sourceTree = ""; }; 39 | 966013B61D81DAFF0066DB1D /* FaceAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FaceAPI.swift; sourceTree = ""; }; 40 | 966013B81D81DB280066DB1D /* Readme.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Readme.md; sourceTree = ""; }; 41 | 96A8C0DD1D81D9F100797080 /* ios-swift-faceapi-sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "ios-swift-faceapi-sample.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | 96A8C0E01D81D9F100797080 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 43 | 96A8C0E71D81D9F100797080 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 44 | 96A8C0EA1D81D9F100797080 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 45 | 96A8C0EC1D81D9F100797080 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 46 | 9E6B7DCF33C7FD5F8EB7EE4C /* Pods-ios-swift-faceapi-sample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ios-swift-faceapi-sample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ios-swift-faceapi-sample/Pods-ios-swift-faceapi-sample.debug.xcconfig"; sourceTree = ""; }; 47 | AABC9B24BB9CA0FB45F203BD /* Pods_ios_swift_faceapi_sample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ios_swift_faceapi_sample.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 48 | /* End PBXFileReference section */ 49 | 50 | /* Begin PBXFrameworksBuildPhase section */ 51 | 96A8C0DA1D81D9F100797080 /* Frameworks */ = { 52 | isa = PBXFrameworksBuildPhase; 53 | buildActionMask = 2147483647; 54 | files = ( 55 | EDD8813B37568D83E0973C98 /* Pods_ios_swift_faceapi_sample.framework in Frameworks */, 56 | ); 57 | runOnlyForDeploymentPostprocessing = 0; 58 | }; 59 | /* End PBXFrameworksBuildPhase section */ 60 | 61 | /* Begin PBXGroup section */ 62 | 6E81A13CDEC0D218748D308D /* Frameworks */ = { 63 | isa = PBXGroup; 64 | children = ( 65 | AABC9B24BB9CA0FB45F203BD /* Pods_ios_swift_faceapi_sample.framework */, 66 | ); 67 | name = Frameworks; 68 | sourceTree = ""; 69 | }; 70 | 9660139C1D81DA730066DB1D /* Services */ = { 71 | isa = PBXGroup; 72 | children = ( 73 | 966013A21D81DA9C0066DB1D /* FaceAPI */, 74 | 966013A11D81DA970066DB1D /* Graph */, 75 | ); 76 | name = Services; 77 | sourceTree = ""; 78 | }; 79 | 9660139D1D81DA760066DB1D /* Controllers */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | 96A8C0E01D81D9F100797080 /* AppDelegate.swift */, 83 | 966013A81D81DAF60066DB1D /* ViewController+Alert.swift */, 84 | 966013A91D81DAF60066DB1D /* ConnectViewController.swift */, 85 | 966013AA1D81DAF60066DB1D /* PhotoSelectorTableViewController.swift */, 86 | 966013AB1D81DAF60066DB1D /* SelectPersonTableViewController.swift */, 87 | 966013AC1D81DAF60066DB1D /* FaceApiTableViewController.swift */, 88 | ); 89 | name = Controllers; 90 | sourceTree = ""; 91 | }; 92 | 9660139E1D81DA770066DB1D /* Views */ = { 93 | isa = PBXGroup; 94 | children = ( 95 | 966013A61D81DABA0066DB1D /* Main.storyboard */, 96 | 96A8C0E91D81D9F100797080 /* LaunchScreen.storyboard */, 97 | ); 98 | name = Views; 99 | sourceTree = ""; 100 | }; 101 | 9660139F1D81DA790066DB1D /* Resources */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | 96A8C0E71D81D9F100797080 /* Assets.xcassets */, 105 | 96A8C0EC1D81D9F100797080 /* Info.plist */, 106 | ); 107 | name = Resources; 108 | sourceTree = ""; 109 | }; 110 | 966013A01D81DA7D0066DB1D /* Application */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | 966013A41D81DAA60066DB1D /* ApplicationConstants.swift */, 114 | ); 115 | name = Application; 116 | sourceTree = ""; 117 | }; 118 | 966013A11D81DA970066DB1D /* Graph */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 966013B21D81DAFB0066DB1D /* AuthenticationProvider.swift */, 122 | 966013B31D81DAFB0066DB1D /* Graph.swift */, 123 | ); 124 | name = Graph; 125 | sourceTree = ""; 126 | }; 127 | 966013A21D81DA9C0066DB1D /* FaceAPI */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | 966013B61D81DAFF0066DB1D /* FaceAPI.swift */, 131 | ); 132 | name = FaceAPI; 133 | sourceTree = ""; 134 | }; 135 | 96A8C0D41D81D9F100797080 = { 136 | isa = PBXGroup; 137 | children = ( 138 | 966013B81D81DB280066DB1D /* Readme.md */, 139 | 96A8C0DF1D81D9F100797080 /* ios-swift-faceapi-sample */, 140 | 96A8C0DE1D81D9F100797080 /* Products */, 141 | AA826BEB9BC8E81CD37DBDD2 /* Pods */, 142 | 6E81A13CDEC0D218748D308D /* Frameworks */, 143 | ); 144 | sourceTree = ""; 145 | }; 146 | 96A8C0DE1D81D9F100797080 /* Products */ = { 147 | isa = PBXGroup; 148 | children = ( 149 | 96A8C0DD1D81D9F100797080 /* ios-swift-faceapi-sample.app */, 150 | ); 151 | name = Products; 152 | sourceTree = ""; 153 | }; 154 | 96A8C0DF1D81D9F100797080 /* ios-swift-faceapi-sample */ = { 155 | isa = PBXGroup; 156 | children = ( 157 | 238BA008227BB14A00A5BACD /* ios-swift-faceapi-sample.entitlements */, 158 | 966013A01D81DA7D0066DB1D /* Application */, 159 | 9660139F1D81DA790066DB1D /* Resources */, 160 | 9660139E1D81DA770066DB1D /* Views */, 161 | 9660139D1D81DA760066DB1D /* Controllers */, 162 | 9660139C1D81DA730066DB1D /* Services */, 163 | ); 164 | path = "ios-swift-faceapi-sample"; 165 | sourceTree = ""; 166 | }; 167 | AA826BEB9BC8E81CD37DBDD2 /* Pods */ = { 168 | isa = PBXGroup; 169 | children = ( 170 | 9E6B7DCF33C7FD5F8EB7EE4C /* Pods-ios-swift-faceapi-sample.debug.xcconfig */, 171 | 3215642D1D267668AD70ABAD /* Pods-ios-swift-faceapi-sample.release.xcconfig */, 172 | ); 173 | name = Pods; 174 | sourceTree = ""; 175 | }; 176 | /* End PBXGroup section */ 177 | 178 | /* Begin PBXNativeTarget section */ 179 | 96A8C0DC1D81D9F100797080 /* ios-swift-faceapi-sample */ = { 180 | isa = PBXNativeTarget; 181 | buildConfigurationList = 96A8C0EF1D81D9F100797080 /* Build configuration list for PBXNativeTarget "ios-swift-faceapi-sample" */; 182 | buildPhases = ( 183 | FFBA0EB15B4CB967A0BF4478 /* [CP] Check Pods Manifest.lock */, 184 | 96A8C0D91D81D9F100797080 /* Sources */, 185 | 96A8C0DA1D81D9F100797080 /* Frameworks */, 186 | 96A8C0DB1D81D9F100797080 /* Resources */, 187 | 38960F5BF400F2762D2618C4 /* [CP] Embed Pods Frameworks */, 188 | ); 189 | buildRules = ( 190 | ); 191 | dependencies = ( 192 | ); 193 | name = "ios-swift-faceapi-sample"; 194 | productName = "ios-swift-faceapi-sample"; 195 | productReference = 96A8C0DD1D81D9F100797080 /* ios-swift-faceapi-sample.app */; 196 | productType = "com.apple.product-type.application"; 197 | }; 198 | /* End PBXNativeTarget section */ 199 | 200 | /* Begin PBXProject section */ 201 | 96A8C0D51D81D9F100797080 /* Project object */ = { 202 | isa = PBXProject; 203 | attributes = { 204 | LastSwiftUpdateCheck = 0730; 205 | LastUpgradeCheck = 1010; 206 | ORGANIZATIONNAME = Microsoft; 207 | TargetAttributes = { 208 | 96A8C0DC1D81D9F100797080 = { 209 | CreatedOnToolsVersion = 7.3.1; 210 | SystemCapabilities = { 211 | com.apple.Keychain = { 212 | enabled = 1; 213 | }; 214 | }; 215 | }; 216 | }; 217 | }; 218 | buildConfigurationList = 96A8C0D81D81D9F100797080 /* Build configuration list for PBXProject "ios-swift-faceapi-sample" */; 219 | compatibilityVersion = "Xcode 3.2"; 220 | developmentRegion = English; 221 | hasScannedForEncodings = 0; 222 | knownRegions = ( 223 | en, 224 | Base, 225 | ); 226 | mainGroup = 96A8C0D41D81D9F100797080; 227 | productRefGroup = 96A8C0DE1D81D9F100797080 /* Products */; 228 | projectDirPath = ""; 229 | projectRoot = ""; 230 | targets = ( 231 | 96A8C0DC1D81D9F100797080 /* ios-swift-faceapi-sample */, 232 | ); 233 | }; 234 | /* End PBXProject section */ 235 | 236 | /* Begin PBXResourcesBuildPhase section */ 237 | 96A8C0DB1D81D9F100797080 /* Resources */ = { 238 | isa = PBXResourcesBuildPhase; 239 | buildActionMask = 2147483647; 240 | files = ( 241 | 96A8C0EB1D81D9F100797080 /* LaunchScreen.storyboard in Resources */, 242 | 96A8C0E81D81D9F100797080 /* Assets.xcassets in Resources */, 243 | 966013A71D81DABA0066DB1D /* Main.storyboard in Resources */, 244 | ); 245 | runOnlyForDeploymentPostprocessing = 0; 246 | }; 247 | /* End PBXResourcesBuildPhase section */ 248 | 249 | /* Begin PBXShellScriptBuildPhase section */ 250 | 38960F5BF400F2762D2618C4 /* [CP] Embed Pods Frameworks */ = { 251 | isa = PBXShellScriptBuildPhase; 252 | buildActionMask = 2147483647; 253 | files = ( 254 | ); 255 | inputFileListPaths = ( 256 | ); 257 | inputPaths = ( 258 | "${PODS_ROOT}/Target Support Files/Pods-ios-swift-faceapi-sample/Pods-ios-swift-faceapi-sample-frameworks.sh", 259 | "${BUILT_PRODUCTS_DIR}/MSAL/MSAL.framework", 260 | "${BUILT_PRODUCTS_DIR}/MSGraphSDK/MSGraphSDK.framework", 261 | ); 262 | name = "[CP] Embed Pods Frameworks"; 263 | outputFileListPaths = ( 264 | ); 265 | outputPaths = ( 266 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MSAL.framework", 267 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MSGraphSDK.framework", 268 | ); 269 | runOnlyForDeploymentPostprocessing = 0; 270 | shellPath = /bin/sh; 271 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ios-swift-faceapi-sample/Pods-ios-swift-faceapi-sample-frameworks.sh\"\n"; 272 | showEnvVarsInLog = 0; 273 | }; 274 | FFBA0EB15B4CB967A0BF4478 /* [CP] Check Pods Manifest.lock */ = { 275 | isa = PBXShellScriptBuildPhase; 276 | buildActionMask = 2147483647; 277 | files = ( 278 | ); 279 | inputPaths = ( 280 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 281 | "${PODS_ROOT}/Manifest.lock", 282 | ); 283 | name = "[CP] Check Pods Manifest.lock"; 284 | outputPaths = ( 285 | "$(DERIVED_FILE_DIR)/Pods-ios-swift-faceapi-sample-checkManifestLockResult.txt", 286 | ); 287 | runOnlyForDeploymentPostprocessing = 0; 288 | shellPath = /bin/sh; 289 | 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"; 290 | showEnvVarsInLog = 0; 291 | }; 292 | /* End PBXShellScriptBuildPhase section */ 293 | 294 | /* Begin PBXSourcesBuildPhase section */ 295 | 96A8C0D91D81D9F100797080 /* Sources */ = { 296 | isa = PBXSourcesBuildPhase; 297 | buildActionMask = 2147483647; 298 | files = ( 299 | 966013B11D81DAF60066DB1D /* FaceApiTableViewController.swift in Sources */, 300 | 966013B01D81DAF60066DB1D /* SelectPersonTableViewController.swift in Sources */, 301 | 966013B91D81DB280066DB1D /* Readme.md in Sources */, 302 | 966013AD1D81DAF60066DB1D /* ViewController+Alert.swift in Sources */, 303 | 96A8C0E11D81D9F100797080 /* AppDelegate.swift in Sources */, 304 | 966013AF1D81DAF60066DB1D /* PhotoSelectorTableViewController.swift in Sources */, 305 | 966013B71D81DAFF0066DB1D /* FaceAPI.swift in Sources */, 306 | 966013A51D81DAA60066DB1D /* ApplicationConstants.swift in Sources */, 307 | 966013B51D81DAFB0066DB1D /* Graph.swift in Sources */, 308 | 966013AE1D81DAF60066DB1D /* ConnectViewController.swift in Sources */, 309 | 966013B41D81DAFB0066DB1D /* AuthenticationProvider.swift in Sources */, 310 | ); 311 | runOnlyForDeploymentPostprocessing = 0; 312 | }; 313 | /* End PBXSourcesBuildPhase section */ 314 | 315 | /* Begin PBXVariantGroup section */ 316 | 96A8C0E91D81D9F100797080 /* LaunchScreen.storyboard */ = { 317 | isa = PBXVariantGroup; 318 | children = ( 319 | 96A8C0EA1D81D9F100797080 /* Base */, 320 | ); 321 | name = LaunchScreen.storyboard; 322 | sourceTree = ""; 323 | }; 324 | /* End PBXVariantGroup section */ 325 | 326 | /* Begin XCBuildConfiguration section */ 327 | 96A8C0ED1D81D9F100797080 /* Debug */ = { 328 | isa = XCBuildConfiguration; 329 | buildSettings = { 330 | ALWAYS_SEARCH_USER_PATHS = NO; 331 | CLANG_ANALYZER_NONNULL = YES; 332 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 333 | CLANG_CXX_LIBRARY = "libc++"; 334 | CLANG_ENABLE_MODULES = YES; 335 | CLANG_ENABLE_OBJC_ARC = YES; 336 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 337 | CLANG_WARN_BOOL_CONVERSION = YES; 338 | CLANG_WARN_COMMA = YES; 339 | CLANG_WARN_CONSTANT_CONVERSION = YES; 340 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 341 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 342 | CLANG_WARN_EMPTY_BODY = YES; 343 | CLANG_WARN_ENUM_CONVERSION = YES; 344 | CLANG_WARN_INFINITE_RECURSION = YES; 345 | CLANG_WARN_INT_CONVERSION = YES; 346 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 347 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 348 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 349 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 350 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 351 | CLANG_WARN_STRICT_PROTOTYPES = YES; 352 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 353 | CLANG_WARN_UNREACHABLE_CODE = YES; 354 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 355 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 356 | COPY_PHASE_STRIP = NO; 357 | DEBUG_INFORMATION_FORMAT = dwarf; 358 | ENABLE_STRICT_OBJC_MSGSEND = YES; 359 | ENABLE_TESTABILITY = YES; 360 | GCC_C_LANGUAGE_STANDARD = gnu99; 361 | GCC_DYNAMIC_NO_PIC = NO; 362 | GCC_NO_COMMON_BLOCKS = YES; 363 | GCC_OPTIMIZATION_LEVEL = 0; 364 | GCC_PREPROCESSOR_DEFINITIONS = ( 365 | "DEBUG=1", 366 | "$(inherited)", 367 | ); 368 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 369 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 370 | GCC_WARN_UNDECLARED_SELECTOR = YES; 371 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 372 | GCC_WARN_UNUSED_FUNCTION = YES; 373 | GCC_WARN_UNUSED_VARIABLE = YES; 374 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 375 | MTL_ENABLE_DEBUG_INFO = YES; 376 | ONLY_ACTIVE_ARCH = YES; 377 | SDKROOT = iphoneos; 378 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 379 | }; 380 | name = Debug; 381 | }; 382 | 96A8C0EE1D81D9F100797080 /* Release */ = { 383 | isa = XCBuildConfiguration; 384 | buildSettings = { 385 | ALWAYS_SEARCH_USER_PATHS = NO; 386 | CLANG_ANALYZER_NONNULL = YES; 387 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 388 | CLANG_CXX_LIBRARY = "libc++"; 389 | CLANG_ENABLE_MODULES = YES; 390 | CLANG_ENABLE_OBJC_ARC = YES; 391 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 392 | CLANG_WARN_BOOL_CONVERSION = YES; 393 | CLANG_WARN_COMMA = YES; 394 | CLANG_WARN_CONSTANT_CONVERSION = YES; 395 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 396 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 397 | CLANG_WARN_EMPTY_BODY = YES; 398 | CLANG_WARN_ENUM_CONVERSION = YES; 399 | CLANG_WARN_INFINITE_RECURSION = YES; 400 | CLANG_WARN_INT_CONVERSION = YES; 401 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 402 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 403 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 404 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 405 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 406 | CLANG_WARN_STRICT_PROTOTYPES = YES; 407 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 408 | CLANG_WARN_UNREACHABLE_CODE = YES; 409 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 410 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 411 | COPY_PHASE_STRIP = NO; 412 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 413 | ENABLE_NS_ASSERTIONS = NO; 414 | ENABLE_STRICT_OBJC_MSGSEND = YES; 415 | GCC_C_LANGUAGE_STANDARD = gnu99; 416 | GCC_NO_COMMON_BLOCKS = YES; 417 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 418 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 419 | GCC_WARN_UNDECLARED_SELECTOR = YES; 420 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 421 | GCC_WARN_UNUSED_FUNCTION = YES; 422 | GCC_WARN_UNUSED_VARIABLE = YES; 423 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 424 | MTL_ENABLE_DEBUG_INFO = NO; 425 | SDKROOT = iphoneos; 426 | SWIFT_COMPILATION_MODE = wholemodule; 427 | VALIDATE_PRODUCT = YES; 428 | }; 429 | name = Release; 430 | }; 431 | 96A8C0F01D81D9F100797080 /* Debug */ = { 432 | isa = XCBuildConfiguration; 433 | baseConfigurationReference = 9E6B7DCF33C7FD5F8EB7EE4C /* Pods-ios-swift-faceapi-sample.debug.xcconfig */; 434 | buildSettings = { 435 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 436 | CLANG_ENABLE_MODULES = YES; 437 | CODE_SIGN_ENTITLEMENTS = "ios-swift-faceapi-sample/ios-swift-faceapi-sample.entitlements"; 438 | DEVELOPMENT_TEAM = ""; 439 | INFOPLIST_FILE = "ios-swift-faceapi-sample/Info.plist"; 440 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 441 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 442 | PRODUCT_BUNDLE_IDENTIFIER = "com.microsoft.ios-swift-faceapi-sample"; 443 | PRODUCT_NAME = "$(TARGET_NAME)"; 444 | SWIFT_OBJC_BRIDGING_HEADER = ""; 445 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 446 | SWIFT_VERSION = 4.2; 447 | }; 448 | name = Debug; 449 | }; 450 | 96A8C0F11D81D9F100797080 /* Release */ = { 451 | isa = XCBuildConfiguration; 452 | baseConfigurationReference = 3215642D1D267668AD70ABAD /* Pods-ios-swift-faceapi-sample.release.xcconfig */; 453 | buildSettings = { 454 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 455 | CLANG_ENABLE_MODULES = YES; 456 | CODE_SIGN_ENTITLEMENTS = "ios-swift-faceapi-sample/ios-swift-faceapi-sample.entitlements"; 457 | DEVELOPMENT_TEAM = ""; 458 | INFOPLIST_FILE = "ios-swift-faceapi-sample/Info.plist"; 459 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 460 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 461 | PRODUCT_BUNDLE_IDENTIFIER = "com.microsoft.ios-swift-faceapi-sample"; 462 | PRODUCT_NAME = "$(TARGET_NAME)"; 463 | SWIFT_OBJC_BRIDGING_HEADER = ""; 464 | SWIFT_VERSION = 4.2; 465 | }; 466 | name = Release; 467 | }; 468 | /* End XCBuildConfiguration section */ 469 | 470 | /* Begin XCConfigurationList section */ 471 | 96A8C0D81D81D9F100797080 /* Build configuration list for PBXProject "ios-swift-faceapi-sample" */ = { 472 | isa = XCConfigurationList; 473 | buildConfigurations = ( 474 | 96A8C0ED1D81D9F100797080 /* Debug */, 475 | 96A8C0EE1D81D9F100797080 /* Release */, 476 | ); 477 | defaultConfigurationIsVisible = 0; 478 | defaultConfigurationName = Release; 479 | }; 480 | 96A8C0EF1D81D9F100797080 /* Build configuration list for PBXNativeTarget "ios-swift-faceapi-sample" */ = { 481 | isa = XCConfigurationList; 482 | buildConfigurations = ( 483 | 96A8C0F01D81D9F100797080 /* Debug */, 484 | 96A8C0F11D81D9F100797080 /* Release */, 485 | ); 486 | defaultConfigurationIsVisible = 0; 487 | defaultConfigurationName = Release; 488 | }; 489 | /* End XCConfigurationList section */ 490 | }; 491 | rootObject = 96A8C0D51D81D9F100797080 /* Project object */; 492 | } 493 | -------------------------------------------------------------------------------- /ios-swift-faceapi-sample/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 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 280 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | --------------------------------------------------------------------------------