Skip to content

Commit

Permalink
Fix white face cutout (#111)
Browse files Browse the repository at this point in the history
* WIP: Fix white face cutout

* set face frame

* Update face framing leniency

* nit

* Better error handling

* Prepare for 10.0.2 release
  • Loading branch information
vanshg authored Dec 20, 2023
1 parent 3242ea2 commit ed2fb15
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 123 deletions.
11 changes: 7 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
## 10.0.2 (unreleased)
## 10.0.3 (unreleased)

### Added

### Fixed

### Changed

### Fixed
- Fixed a bug on iOS 14 devices where the document cutout showed a white box instead
### Remved

### Removed
## 10.0.2

### Fixed
- Fixed a bug on iOS 14 devices where the document and selfie cutouts were white insteaad of transparent

## 10.0.1

Expand Down
4 changes: 2 additions & 2 deletions Example/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
PODS:
- netfox (1.21.0)
- SmileID (10.0.1):
- SmileID (10.0.2):
- Zip (~> 2.1.0)
- SwiftLint (0.54.0)
- Zip (2.1.2)
Expand All @@ -22,7 +22,7 @@ EXTERNAL SOURCES:

SPEC CHECKSUMS:
netfox: 9d5cc727fe7576c4c7688a2504618a156b7d44b7
SmileID: fe0c278666c4f09a3e8f85763a2634569d44f768
SmileID: fe52d6b0686bc56e3caa0b89a79943b0710bfd8e
SwiftLint: c1de071d9d08c8aba837545f6254315bc900e211
Zip: b3fef584b147b6e582b2256a9815c897d60ddc67

Expand Down
4 changes: 2 additions & 2 deletions SmileID.podspec
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
Pod::Spec.new do |s|
s.name = 'SmileID'
s.version = '10.0.1'
s.version = '10.0.2'
s.summary = 'The Official Smile Identity iOS SDK.'
s.homepage = 'https://docs.usesmileid.com/integration-options/mobile/ios-v10-beta'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'Japhet' => '[email protected]', 'Juma Allan' => '[email protected]', 'Vansh Gandhi' => '[email protected]'}
s.source = { :git => "https://github.com/smileidentity/ios.git", :tag => "v10.0.1" }
s.source = { :git => "https://github.com/smileidentity/ios.git", :tag => "v10.0.2" }
s.ios.deployment_target = '13.0'
s.dependency 'Zip', '~> 2.1.0'
s.swift_version = '5.5'
Expand Down
108 changes: 34 additions & 74 deletions Sources/SmileID/Classes/SelfieCapture/View/FaceOverlayView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,79 +4,30 @@ import SwiftUI

struct FaceOverlayView: View {
@ObservedObject private(set) var model: SelfieCaptureViewModel
var body: some View {
GeometryReader { geometry in
let faceWidth = geometry.size.width * 0.6
let faceHeight = faceWidth / 0.7

VStack(spacing: 5) {
ZStack {
Rectangle()
.fill(Color.white.opacity(0.8))
.frame(maxWidth: .infinity, maxHeight: .infinity)
.overlay(
FaceShape()
.blendMode(.destinationOut)
.frame(width: faceWidth, height: faceHeight)
.background(GeometryReader { localGeometry in
Color.clear.onReceive(
// The delay is needed for when the view could be going
// through a transition (i.e. resizing because the keyboard
// is in the process of getting dismissed)
Just(localGeometry.frame(in: .global))
.delay(for: 0.5, scheduler: DispatchQueue.main)
) { globalFrame in
let globalOriginX = globalFrame.origin.x
let faceOriginX = model.faceLayoutGuideFrame.origin.x
if globalOriginX != faceOriginX {
let window = UIApplication
.shared
.connectedScenes
.flatMap { ($0 as? UIWindowScene)?.windows ?? [] }
.last { $0.isKeyWindow }
if let rootView = window {
// Geometry reader's .global returns the frame in
// the screen's coordinate system.
let screenHeight = rootView.screen.bounds.height
let geometryHeight = geometry.size.height
let safeArea = screenHeight - geometryHeight
model.faceLayoutGuideFrame = CGRect(
origin: CGPoint(
x: globalOriginX,
y: globalFrame.origin.y - safeArea
),
size: globalFrame.size
)
}
}
}
})
)
.overlay(
FaceShape()
.stroke(
SmileID.theme.accent.opacity(0.4),
lineWidth: 10
)
.frame(width: faceWidth, height: faceHeight))
.overlay(
FaceShape()
.trim(from: 0, to: model.progress)
.stroke(
SmileID.theme.success,
style: StrokeStyle(
lineWidth: 10,
lineCap: .round
)
)
.frame(width: faceWidth, height: faceHeight)
.animation(.easeOut, value: model.progress)
private let agentModeHeight = 48.0
private let strokeWidth = 10
private let faceShape = FaceShape().scale(x: 0.8, y: 0.7).offset(y: -20)
private let bgColor = Color.white.opacity(0.8)

var body: some View {
let view = VStack(spacing: 0) {
bgColor
.cutout(faceShape)
.overlay(faceShape.stroke(SmileID.theme.accent.opacity(0.4), lineWidth: 10))
.overlay(
faceShape
.trim(from: 0, to: model.progress)
.stroke(
SmileID.theme.success,
style: StrokeStyle(lineWidth: 10, lineCap: .round)
)
}
.padding(.top, -200)
.scaleEffect(1.2, anchor: .top)
.animation(.easeOut, value: model.progress)
)

VStack(spacing: 25) {
InstructionsView(model: model)
.padding(.top, -((faceWidth) / 2))

if model.allowsAgentMode {
let agentMode = model.agentMode
HStack(spacing: 10) {
Expand All @@ -87,14 +38,23 @@ struct FaceOverlayView: View {
.font(SmileID.theme.header4)
Toggle("", isOn: $model.agentMode).labelsHidden()
}
.frame(width: 188, height: 46)
.frame(width: 188, height: agentModeHeight)
.background(agentMode ? SmileID.theme.accent : SmileID.theme.backgroundMain)
.cornerRadius(23)
.shadow(radius: 23)
.padding(.bottom, 35)
.cornerRadius(25)
.shadow(radius: 25)
.animation(.default)
}
}
// Fixed height to accomodate for scaling based face outline
.frame(maxWidth: .infinity, maxHeight: agentModeHeight)
.padding(.bottom, 25)
.background(bgColor)
}

if #available(iOS 14.0, *) {
view.ignoresSafeArea(.keyboard)
} else {
view
}
}
}
44 changes: 19 additions & 25 deletions Sources/SmileID/Classes/SelfieCapture/View/SelfieCaptureView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,31 @@ public struct SelfieCaptureView: View, SelfieViewDelegate {
camera = CameraView(cameraManager: viewModel.cameraManager)
arView = nil
}

let window = UIApplication
.shared
.connectedScenes
.flatMap { ($0 as? UIWindowScene)?.windows ?? [] }
.last { $0.isKeyWindow }
if let rootView = window {
viewModel.faceLayoutGuideFrame = rootView.screen.bounds
} else {
print("window was unexpectedly null -- selfie capture will not work")
viewModel.handleError(SmileIDError.unknown("Unable to capture selfie"))
}
}

public var body: some View {
GeometryReader { geometry in
ZStack {
if ARFaceTrackingConfiguration.isSupported && viewModel.agentMode == false {
let didDetectBounds = viewModel.faceLayoutGuideFrame != CGRect.zero
if didDetectBounds && ARFaceTrackingConfiguration.isSupported && !viewModel.agentMode {
arView.onAppear {
arView?.preview.model = viewModel
viewModel.viewFinderSize = geometry.size
viewModel.selfieViewDelegate = self
}
} else {
} else if didDetectBounds {
camera.onAppear {
viewModel.smartSelfieResultDelegate = delegate
viewModel.viewDelegate = camera!.preview
Expand All @@ -50,7 +63,9 @@ public struct SelfieCaptureView: View, SelfieViewDelegate {
)
}
}
faceOverlay

faceOverlay.frame(width: geometry.size.width)

switch viewModel.processingState {
case .confirmation(let selfieImage):
ModalPresenter {
Expand All @@ -64,7 +79,7 @@ public struct SelfieCaptureView: View, SelfieViewDelegate {
)
}
case .inProgress:
ModalPresenter(centered: true) {
ModalPresenter {
ProcessingView(
image: SmileIDResourcesHelper.FaceOutline,
titleKey: "Confirmation.ProcessingSelfie",
Expand Down Expand Up @@ -93,10 +108,6 @@ public struct SelfieCaptureView: View, SelfieViewDelegate {
}
}

private func ovalSize(from geometry: GeometryProxy) -> CGSize {
CGSize(width: geometry.size.width * 0.6, height: geometry.size.width * 0.6 / 0.7)
}

func pauseARSession() {
arView?.preview.pauseSession()
}
Expand All @@ -105,20 +116,3 @@ public struct SelfieCaptureView: View, SelfieViewDelegate {
arView?.preview.resumeSession()
}
}

struct FaceBoundingBoxView: View {
@ObservedObject private(set) var model: SelfieCaptureViewModel

var body: some View {
switch model.faceGeometryState {
case .faceNotFound:
Rectangle().fill(Color.clear)
case .faceFound(let faceGeometryModel):
Rectangle()
.path(in: faceGeometryModel.boundingBox)
.stroke(Color.yellow, lineWidth: 2.0)
case .errored:
Rectangle().fill(Color.yellow)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ final class SelfieCaptureViewModel: ObservableObject, JobSubmittable, Confirmati
}
}

private func handleError(_ error: SmileIDError) {
func handleError(_ error: SmileIDError) {
switch error {
case let .request(urlError):
processingState = .error(urlError)
Expand Down Expand Up @@ -516,9 +516,15 @@ final class SelfieCaptureViewModel: ObservableObject, JobSubmittable, Confirmati
submit()
}

/// Called on Error Close only
func handleClose() {
pauseCameraSession()
processingState = .endFlow
switch processingState {
case .error(let error):
smartSelfieResultDelegate?.didError(error: error)
default:
handleCompletion()
}
}

func handleCompletion() {
Expand Down Expand Up @@ -636,24 +642,32 @@ extension SelfieCaptureViewModel {
}

func updateAcceptableBounds(using boundingBox: CGRect) {
if boundingBox.width > (0.80 * faceLayoutGuideFrame.width) {
let faceCenter = CGPoint(x: faceLayoutGuideFrame.midX, y: faceLayoutGuideFrame.midY)
let boxCenter = CGPoint(x: boundingBox.midX, y: boundingBox.midY)
let xOffset = abs(faceCenter.x - boxCenter.x)
let yOffset = abs(faceCenter.y - boxCenter.y)
let widthMargin = faceLayoutGuideFrame.width * 0.15
let heightMargin = faceLayoutGuideFrame.height * 0.15
let isFaceInFrame = boundingBox.minX >= faceLayoutGuideFrame.minX &&
boundingBox.maxX <= faceLayoutGuideFrame.maxX &&
boundingBox.maxY <= faceLayoutGuideFrame.maxY &&
boundingBox.minY >= faceLayoutGuideFrame.minY

if !isFaceInFrame {
isAcceptableBounds = .detectedFaceOffCentre
subject.send("Instructions.Start")
resetCapture()
} else if xOffset > widthMargin || yOffset > heightMargin {
isAcceptableBounds = .detectedFaceOffCentre
subject.send("Instructions.Start")
} else if boundingBox.width > (0.7 * faceLayoutGuideFrame.width) {
isAcceptableBounds = .detectedFaceTooLarge
subject.send("Instructions.FaceClose")
} else if boundingBox.width < (faceLayoutGuideFrame.width * 0.25) {
} else if boundingBox.width < (0.5 * faceLayoutGuideFrame.width) {
isAcceptableBounds = .detectedFaceTooSmall
subject.send("Instructions.FaceFar")
} else {
let isFaceInFrame = boundingBox.minX >= faceLayoutGuideFrame.minX &&
boundingBox.maxX <= faceLayoutGuideFrame.maxX &&
boundingBox.maxY <= faceLayoutGuideFrame.maxY &&
boundingBox.minY >= faceLayoutGuideFrame.minY
if !isFaceInFrame {
isAcceptableBounds = .detectedFaceOffCentre
subject.send("Instructions.Start")
resetCapture()
} else {
isAcceptableBounds = .detectedFaceAppropriateSizeAndPosition
}
isAcceptableBounds = .detectedFaceAppropriateSizeAndPosition
}
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/SmileID/Classes/SmileID.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import SwiftUI
import UIKit

public class SmileID {
public static let version = "10.0.1"
public static let version = "10.0.2"
@Injected var injectedApi: SmileIDServiceable
public static var configuration: Config { config }

Expand Down

0 comments on commit ed2fb15

Please sign in to comment.