├── 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 |  
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 |
--------------------------------------------------------------------------------