-
Notifications
You must be signed in to change notification settings - Fork 55
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
BUG-001: Fixes bug when returning from navigation detail view and add…
…s demo app.
- Loading branch information
Rolando Rodríguez
committed
Aug 25, 2022
1 parent
2b72809
commit a83ad04
Showing
13 changed files
with
1,097 additions
and
12 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
7 changes: 7 additions & 0 deletions
7
DemoApp/DemoApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
8 changes: 8 additions & 0 deletions
8
DemoApp/DemoApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
<plist version="1.0"> | ||
<dict> | ||
<key>IDEDidComputeMac32BitWarning</key> | ||
<true/> | ||
</dict> | ||
</plist> |
11 changes: 11 additions & 0 deletions
11
DemoApp/DemoApp/Assets.xcassets/AccentColor.colorset/Contents.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"colors" : [ | ||
{ | ||
"idiom" : "universal" | ||
} | ||
], | ||
"info" : { | ||
"author" : "xcode", | ||
"version" : 1 | ||
} | ||
} |
93 changes: 93 additions & 0 deletions
93
DemoApp/DemoApp/Assets.xcassets/AppIcon.appiconset/Contents.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
{ | ||
"images" : [ | ||
{ | ||
"idiom" : "iphone", | ||
"scale" : "2x", | ||
"size" : "20x20" | ||
}, | ||
{ | ||
"idiom" : "iphone", | ||
"scale" : "3x", | ||
"size" : "20x20" | ||
}, | ||
{ | ||
"idiom" : "iphone", | ||
"scale" : "2x", | ||
"size" : "29x29" | ||
}, | ||
{ | ||
"idiom" : "iphone", | ||
"scale" : "3x", | ||
"size" : "29x29" | ||
}, | ||
{ | ||
"idiom" : "iphone", | ||
"scale" : "2x", | ||
"size" : "40x40" | ||
}, | ||
{ | ||
"idiom" : "iphone", | ||
"scale" : "3x", | ||
"size" : "40x40" | ||
}, | ||
{ | ||
"idiom" : "iphone", | ||
"scale" : "2x", | ||
"size" : "60x60" | ||
}, | ||
{ | ||
"idiom" : "iphone", | ||
"scale" : "3x", | ||
"size" : "60x60" | ||
}, | ||
{ | ||
"idiom" : "ipad", | ||
"scale" : "1x", | ||
"size" : "20x20" | ||
}, | ||
{ | ||
"idiom" : "ipad", | ||
"scale" : "2x", | ||
"size" : "20x20" | ||
}, | ||
{ | ||
"idiom" : "ipad", | ||
"scale" : "1x", | ||
"size" : "29x29" | ||
}, | ||
{ | ||
"idiom" : "ipad", | ||
"scale" : "2x", | ||
"size" : "29x29" | ||
}, | ||
{ | ||
"idiom" : "ipad", | ||
"scale" : "1x", | ||
"size" : "40x40" | ||
}, | ||
{ | ||
"idiom" : "ipad", | ||
"scale" : "2x", | ||
"size" : "40x40" | ||
}, | ||
{ | ||
"idiom" : "ipad", | ||
"scale" : "2x", | ||
"size" : "76x76" | ||
}, | ||
{ | ||
"idiom" : "ipad", | ||
"scale" : "2x", | ||
"size" : "83.5x83.5" | ||
}, | ||
{ | ||
"idiom" : "ios-marketing", | ||
"scale" : "1x", | ||
"size" : "1024x1024" | ||
} | ||
], | ||
"info" : { | ||
"author" : "xcode", | ||
"version" : 1 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"info" : { | ||
"author" : "xcode", | ||
"version" : 1 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,206 @@ | ||
// | ||
// ContentView.swift | ||
// DemoApp | ||
// | ||
// Created by Rolando Rodriguez on 8/25/22. | ||
// | ||
|
||
import SwiftUI | ||
import Combine | ||
import Camera_SwiftUI | ||
import AVFoundation | ||
|
||
final class CameraModel: ObservableObject { | ||
private let service = CameraService() | ||
|
||
@Published var photo: Photo! | ||
|
||
@Published var showAlertError = false | ||
|
||
@Published var isFlashOn = false | ||
|
||
@Published var willCapturePhoto = false | ||
|
||
var alertError: AlertError! | ||
|
||
var session: AVCaptureSession | ||
|
||
private var subscriptions = Set<AnyCancellable>() | ||
|
||
init() { | ||
self.session = service.session | ||
|
||
service.$photo.sink { [weak self] (photo) in | ||
guard let pic = photo else { return } | ||
self?.photo = pic | ||
} | ||
.store(in: &self.subscriptions) | ||
|
||
service.$shouldShowAlertView.sink { [weak self] (val) in | ||
self?.alertError = self?.service.alertError | ||
self?.showAlertError = val | ||
} | ||
.store(in: &self.subscriptions) | ||
|
||
service.$flashMode.sink { [weak self] (mode) in | ||
self?.isFlashOn = mode == .on | ||
} | ||
.store(in: &self.subscriptions) | ||
|
||
service.$willCapturePhoto.sink { [weak self] (val) in | ||
self?.willCapturePhoto = val | ||
} | ||
.store(in: &self.subscriptions) | ||
} | ||
|
||
func configure() { | ||
service.checkForPermissions() | ||
service.configure() | ||
} | ||
|
||
func capturePhoto() { | ||
service.capturePhoto() | ||
} | ||
|
||
func flipCamera() { | ||
service.changeCamera() | ||
} | ||
|
||
func zoom(with factor: CGFloat) { | ||
service.set(zoom: factor) | ||
} | ||
|
||
func switchFlash() { | ||
service.flashMode = service.flashMode == .on ? .off : .on | ||
} | ||
} | ||
|
||
struct CameraView: View { | ||
@StateObject var model = CameraModel() | ||
|
||
@State var currentZoomFactor: CGFloat = 1.0 | ||
|
||
var captureButton: some View { | ||
Button(action: { | ||
model.capturePhoto() | ||
}, label: { | ||
Circle() | ||
.foregroundColor(.white) | ||
.frame(width: 80, height: 80, alignment: .center) | ||
.overlay( | ||
Circle() | ||
.stroke(Color.black.opacity(0.8), lineWidth: 2) | ||
.frame(width: 65, height: 65, alignment: .center) | ||
) | ||
}) | ||
} | ||
|
||
var capturedPhotoThumbnail: some View { | ||
Group { | ||
if model.photo != nil { | ||
Image(uiImage: model.photo.image!) | ||
.resizable() | ||
.aspectRatio(contentMode: .fill) | ||
.frame(width: 60, height: 60) | ||
.clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous)) | ||
.animation(.spring()) | ||
|
||
} else { | ||
RoundedRectangle(cornerRadius: 10) | ||
.frame(width: 60, height: 60, alignment: .center) | ||
.foregroundColor(.black) | ||
} | ||
} | ||
} | ||
|
||
var flipCameraButton: some View { | ||
Button(action: { | ||
model.flipCamera() | ||
}, label: { | ||
Circle() | ||
.foregroundColor(Color.gray.opacity(0.2)) | ||
.frame(width: 45, height: 45, alignment: .center) | ||
.overlay( | ||
Image(systemName: "camera.rotate.fill") | ||
.foregroundColor(.white)) | ||
}) | ||
} | ||
|
||
var body: some View { | ||
NavigationView { | ||
GeometryReader { reader in | ||
ZStack { | ||
Color.black.edgesIgnoringSafeArea(.all) | ||
|
||
VStack { | ||
Button(action: { | ||
model.switchFlash() | ||
}, label: { | ||
Image(systemName: model.isFlashOn ? "bolt.fill" : "bolt.slash.fill") | ||
.font(.system(size: 20, weight: .medium, design: .default)) | ||
}) | ||
.accentColor(model.isFlashOn ? .yellow : .white) | ||
|
||
CameraPreview(session: model.session) | ||
.gesture( | ||
DragGesture().onChanged({ (val) in | ||
// Only accept vertical drag | ||
if abs(val.translation.height) > abs(val.translation.width) { | ||
// Get the percentage of vertical screen space covered by drag | ||
let percentage: CGFloat = -(val.translation.height / reader.size.height) | ||
// Calculate new zoom factor | ||
let calc = currentZoomFactor + percentage | ||
// Limit zoom factor to a maximum of 5x and a minimum of 1x | ||
let zoomFactor: CGFloat = min(max(calc, 1), 5) | ||
// Store the newly calculated zoom factor | ||
currentZoomFactor = zoomFactor | ||
// Sets the zoom factor to the capture device session | ||
model.zoom(with: zoomFactor) | ||
} | ||
}) | ||
) | ||
.onAppear { | ||
model.configure() | ||
} | ||
.alert(isPresented: $model.showAlertError, content: { | ||
Alert(title: Text(model.alertError.title), message: Text(model.alertError.message), dismissButton: .default(Text(model.alertError.primaryButtonTitle), action: { | ||
model.alertError.primaryAction?() | ||
})) | ||
}) | ||
.overlay( | ||
Group { | ||
if model.willCapturePhoto { | ||
Color.black | ||
} | ||
} | ||
) | ||
.animation(.easeInOut) | ||
|
||
|
||
HStack { | ||
NavigationLink(destination: Text("Detail photo")) { | ||
capturedPhotoThumbnail | ||
} | ||
|
||
Spacer() | ||
|
||
captureButton | ||
|
||
Spacer() | ||
|
||
flipCameraButton | ||
|
||
} | ||
.padding(.horizontal, 20) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
struct ContentView_Previews: PreviewProvider { | ||
static var previews: some View { | ||
CameraView() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
// | ||
// DemoAppApp.swift | ||
// DemoApp | ||
// | ||
// Created by Rolando Rodriguez on 8/25/22. | ||
// | ||
|
||
import SwiftUI | ||
|
||
@main | ||
struct DemoAppApp: App { | ||
var body: some Scene { | ||
WindowGroup { | ||
CameraView() | ||
} | ||
} | ||
} |
6 changes: 6 additions & 0 deletions
6
DemoApp/DemoApp/Preview Content/Preview Assets.xcassets/Contents.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"info" : { | ||
"author" : "xcode", | ||
"version" : 1 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
// | ||
// DemoAppTests.swift | ||
// DemoAppTests | ||
// | ||
// Created by Rolando Rodriguez on 8/25/22. | ||
// | ||
|
||
import XCTest | ||
@testable import DemoApp | ||
|
||
class DemoAppTests: XCTestCase { | ||
|
||
override func setUpWithError() throws { | ||
// Put setup code here. This method is called before the invocation of each test method in the class. | ||
} | ||
|
||
override func tearDownWithError() throws { | ||
// Put teardown code here. This method is called after the invocation of each test method in the class. | ||
} | ||
|
||
func testExample() throws { | ||
// This is an example of a functional test case. | ||
// Use XCTAssert and related functions to verify your tests produce the correct results. | ||
// Any test you write for XCTest can be annotated as throws and async. | ||
// Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. | ||
// Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. | ||
} | ||
|
||
func testPerformanceExample() throws { | ||
// This is an example of a performance test case. | ||
self.measure { | ||
// Put the code you want to measure the time of here. | ||
} | ||
} | ||
|
||
} |
Oops, something went wrong.