Implement ScannerViewController and new Contact creation.
This commit is contained in:
parent
2546be8bcb
commit
43baab40d4
|
@ -40,6 +40,11 @@
|
|||
F3203BEF23CC985100265268 /* UserQRCodeFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3203BEE23CC985100265268 /* UserQRCodeFormView.swift */; };
|
||||
F3203BF123CC9B1600265268 /* UserQRCodeFormView+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3203BF023CC9B1600265268 /* UserQRCodeFormView+ViewModel.swift */; };
|
||||
F3203BF423CD140100265268 /* ContactQRCodeRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3203BF323CD140100265268 /* ContactQRCodeRepresentable.swift */; };
|
||||
F3203BFB23CD430500265268 /* QRCodeScannerView+Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3203BF923CD430500265268 /* QRCodeScannerView+Coordinator.swift */; };
|
||||
F3203BFC23CD430500265268 /* QRCodeScannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3203BFA23CD430500265268 /* QRCodeScannerView.swift */; };
|
||||
F3203BFF23CDF6D100265268 /* ScannerViewControlller.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3203BFE23CDF6D100265268 /* ScannerViewControlller.swift */; };
|
||||
F326140823CE3928009EC215 /* ContactsState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F326140723CE3928009EC215 /* ContactsState.swift */; };
|
||||
F326140A23CE689A009EC215 /* Contact+InitHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = F326140923CE689A009EC215 /* Contact+InitHelpers.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
|
@ -75,6 +80,11 @@
|
|||
F3203BEE23CC985100265268 /* UserQRCodeFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserQRCodeFormView.swift; sourceTree = "<group>"; };
|
||||
F3203BF023CC9B1600265268 /* UserQRCodeFormView+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserQRCodeFormView+ViewModel.swift"; sourceTree = "<group>"; };
|
||||
F3203BF323CD140100265268 /* ContactQRCodeRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactQRCodeRepresentable.swift; sourceTree = "<group>"; };
|
||||
F3203BF923CD430500265268 /* QRCodeScannerView+Coordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QRCodeScannerView+Coordinator.swift"; sourceTree = "<group>"; };
|
||||
F3203BFA23CD430500265268 /* QRCodeScannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeScannerView.swift; sourceTree = "<group>"; };
|
||||
F3203BFE23CDF6D100265268 /* ScannerViewControlller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScannerViewControlller.swift; sourceTree = "<group>"; };
|
||||
F326140723CE3928009EC215 /* ContactsState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsState.swift; sourceTree = "<group>"; };
|
||||
F326140923CE689A009EC215 /* Contact+InitHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Contact+InitHelpers.swift"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
@ -134,10 +144,10 @@
|
|||
F3203B9823C760BF00265268 /* App */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F3203B9D23C7610400265268 /* LaunchScreen.storyboard */,
|
||||
F3203B8323C7609500265268 /* SceneDelegate.swift */,
|
||||
F3203B8123C7609500265268 /* AppDelegate.swift */,
|
||||
F3203BA923C82CB100265268 /* CurrentApplication.swift */,
|
||||
F3203B8323C7609500265268 /* SceneDelegate.swift */,
|
||||
F3203B9D23C7610400265268 /* LaunchScreen.storyboard */,
|
||||
);
|
||||
path = App;
|
||||
sourceTree = "<group>";
|
||||
|
@ -145,9 +155,9 @@
|
|||
F3203B9923C760C300265268 /* Scenes */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F3203BB023C8345400265268 /* RootView.swift */,
|
||||
F3203BAD23C8339400265268 /* Collected Contacts */,
|
||||
F3203BAC23C8337F00265268 /* User QR Code */,
|
||||
F3203BB023C8345400265268 /* RootView.swift */,
|
||||
);
|
||||
path = Scenes;
|
||||
sourceTree = "<group>";
|
||||
|
@ -155,10 +165,10 @@
|
|||
F3203B9A23C760C700265268 /* Data */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F3203BAB23C831E300265268 /* Models */,
|
||||
F3203BA823C82C8F00265268 /* State */,
|
||||
F3203B8523C7609500265268 /* QRConnections.xcdatamodeld */,
|
||||
F3203BA623C7636600265268 /* CoreDataManager+Utils.swift */,
|
||||
F3203BAB23C831E300265268 /* Models */,
|
||||
F3203B8523C7609500265268 /* QRConnections.xcdatamodeld */,
|
||||
F3203BA823C82C8F00265268 /* State */,
|
||||
);
|
||||
path = Data;
|
||||
sourceTree = "<group>";
|
||||
|
@ -166,8 +176,9 @@
|
|||
F3203B9B23C760C900265268 /* Reusables */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F3203BF223CD13F300265268 /* Protocols */,
|
||||
F3203BF823CD42F400265268 /* UIKit Wrappers */,
|
||||
F3203BD023C9DFDF00265268 /* ImageFilterService.swift */,
|
||||
F3203BF223CD13F300265268 /* Protocols */,
|
||||
);
|
||||
path = Reusables;
|
||||
sourceTree = "<group>";
|
||||
|
@ -185,6 +196,7 @@
|
|||
children = (
|
||||
F3203BD223C9F20C00265268 /* AppState.swift */,
|
||||
F3203BD423C9F3E900265268 /* UserProfileState.swift */,
|
||||
F326140723CE3928009EC215 /* ContactsState.swift */,
|
||||
);
|
||||
path = State;
|
||||
sourceTree = "<group>";
|
||||
|
@ -200,11 +212,11 @@
|
|||
F3203BAC23C8337F00265268 /* User QR Code */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F3203BCE23C9DAE000265268 /* UserQRCodeView.swift */,
|
||||
F3203BEA23CC977000265268 /* UserQRCodeContainerView.swift */,
|
||||
F3203BEE23CC985100265268 /* UserQRCodeFormView.swift */,
|
||||
F3203BEC23CC97DC00265268 /* UserQRCodeContainerView+ViewModel.swift */,
|
||||
F3203BEE23CC985100265268 /* UserQRCodeFormView.swift */,
|
||||
F3203BF023CC9B1600265268 /* UserQRCodeFormView+ViewModel.swift */,
|
||||
F3203BCE23C9DAE000265268 /* UserQRCodeView.swift */,
|
||||
);
|
||||
path = "User QR Code";
|
||||
sourceTree = "<group>";
|
||||
|
@ -213,8 +225,8 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
F3203BB223C8368800265268 /* ContactsContainerView.swift */,
|
||||
F3203BB623C844F100265268 /* ContactsListView.swift */,
|
||||
F3203BB423C841A700265268 /* ContactsContainerView+ViewModel.swift */,
|
||||
F3203BB623C844F100265268 /* ContactsListView.swift */,
|
||||
);
|
||||
path = "Collected Contacts";
|
||||
sourceTree = "<group>";
|
||||
|
@ -225,6 +237,7 @@
|
|||
F3203BB823C8467000265268 /* Contact+FilterState.swift */,
|
||||
F3203BBB23C8579400265268 /* Contact+CoreDataClass.swift */,
|
||||
F3203BBC23C8579400265268 /* Contact+CoreDataProperties.swift */,
|
||||
F326140923CE689A009EC215 /* Contact+InitHelpers.swift */,
|
||||
F3203BC123C8588E00265268 /* Contact+FetchHelpers.swift */,
|
||||
F3203BBF23C8584500265268 /* Contact+Status.swift */,
|
||||
);
|
||||
|
@ -249,6 +262,24 @@
|
|||
path = Protocols;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F3203BF823CD42F400265268 /* UIKit Wrappers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F3203BFD23CDF68D00265268 /* Scanner View */,
|
||||
);
|
||||
path = "UIKit Wrappers";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F3203BFD23CDF68D00265268 /* Scanner View */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F3203BF923CD430500265268 /* QRCodeScannerView+Coordinator.swift */,
|
||||
F3203BFA23CD430500265268 /* QRCodeScannerView.swift */,
|
||||
F3203BFE23CDF6D100265268 /* ScannerViewControlller.swift */,
|
||||
);
|
||||
path = "Scanner View";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
|
@ -334,11 +365,13 @@
|
|||
F3203BB323C8368800265268 /* ContactsContainerView.swift in Sources */,
|
||||
F3203BC923C871D300265268 /* PreviewDevice+Utils.swift in Sources */,
|
||||
F3203BB923C8467000265268 /* Contact+FilterState.swift in Sources */,
|
||||
F3203BFB23CD430500265268 /* QRCodeScannerView+Coordinator.swift in Sources */,
|
||||
F3203BC223C8588F00265268 /* Contact+FetchHelpers.swift in Sources */,
|
||||
F3203BC023C8584500265268 /* Contact+Status.swift in Sources */,
|
||||
F3203BA723C7636600265268 /* CoreDataManager+Utils.swift in Sources */,
|
||||
F3203BCF23C9DAE100265268 /* UserQRCodeView.swift in Sources */,
|
||||
F3203B8223C7609500265268 /* AppDelegate.swift in Sources */,
|
||||
F326140823CE3928009EC215 /* ContactsState.swift in Sources */,
|
||||
F3203BB123C8345400265268 /* RootView.swift in Sources */,
|
||||
F3203BC723C8717F00265268 /* SampleData+Contacts.swift in Sources */,
|
||||
F3203BD323C9F20C00265268 /* AppState.swift in Sources */,
|
||||
|
@ -346,9 +379,11 @@
|
|||
F3203BB723C844F100265268 /* ContactsListView.swift in Sources */,
|
||||
F3203BED23CC97DC00265268 /* UserQRCodeContainerView+ViewModel.swift in Sources */,
|
||||
F3203BB523C841A700265268 /* ContactsContainerView+ViewModel.swift in Sources */,
|
||||
F326140A23CE689A009EC215 /* Contact+InitHelpers.swift in Sources */,
|
||||
F3203BEB23CC977000265268 /* UserQRCodeContainerView.swift in Sources */,
|
||||
F3203BF123CC9B1600265268 /* UserQRCodeFormView+ViewModel.swift in Sources */,
|
||||
F3203BEF23CC985100265268 /* UserQRCodeFormView.swift in Sources */,
|
||||
F3203BFF23CDF6D100265268 /* ScannerViewControlller.swift in Sources */,
|
||||
F3203BE223CA31EB00265268 /* SampleData+AppStore.swift in Sources */,
|
||||
F3203BD123C9DFDF00265268 /* ImageFilterService.swift in Sources */,
|
||||
F3203B8723C7609500265268 /* QRConnections.xcdatamodeld in Sources */,
|
||||
|
@ -357,6 +392,7 @@
|
|||
F3203BC523C8714700265268 /* SampleData.swift in Sources */,
|
||||
F3203BD523C9F3E900265268 /* UserProfileState.swift in Sources */,
|
||||
F3203B8423C7609500265268 /* SceneDelegate.swift in Sources */,
|
||||
F3203BFC23CD430500265268 /* QRCodeScannerView.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
"repositoryURL": "https://github.com/CypherPoet/CypherPoetCoreDataKit.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "5400fc3983f4174cd36cc79b24b7ef54affecf6b",
|
||||
"version": "0.0.5"
|
||||
"revision": "e14a3abf3e679d41c9e0fcc55d3e4d7ccd8ce1b6",
|
||||
"version": "0.0.6"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import UIKit
|
||||
import CoreData
|
||||
|
||||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// Contact+InitHelpers.swift
|
||||
// QRConnections
|
||||
//
|
||||
// Created by CypherPoet on 1/14/20.
|
||||
// ✌️
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
extension Contact {
|
||||
|
||||
static func make(
|
||||
fromName name: String,
|
||||
andStatus status: Status = .uncontacted,
|
||||
using context: NSManagedObjectContext
|
||||
) -> Contact {
|
||||
let contact = Contact(context: context)
|
||||
|
||||
contact.name = name
|
||||
contact.uuid = UUID()
|
||||
contact.status = status
|
||||
|
||||
return contact
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@ import CypherPoetSwiftUIKit_DataFlowUtils
|
|||
|
||||
|
||||
struct AppState {
|
||||
var contactsState = ContactsState()
|
||||
var userProfileState = UserProfileState()
|
||||
}
|
||||
|
||||
|
@ -22,6 +23,7 @@ struct AppState {
|
|||
|
||||
|
||||
enum AppAction {
|
||||
case contacts(_ action: ContactsAction)
|
||||
case userProfile(_ action: UserProfileAction)
|
||||
}
|
||||
|
||||
|
@ -31,6 +33,8 @@ let appReducer = Reducer<AppState, AppAction> { appState, action in
|
|||
switch action {
|
||||
case .userProfile(let action):
|
||||
userProfileReducer.reduce(&appState.userProfileState, action)
|
||||
case .contacts(let action):
|
||||
contactsReducer.reduce(&appState.contactsState, action)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
//
|
||||
// ContactsState.swift
|
||||
// QRConnections
|
||||
//
|
||||
// Created by CypherPoet on 1/14/20.
|
||||
// ✌️
|
||||
//
|
||||
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
import CypherPoetSwiftUIKit_DataFlowUtils
|
||||
|
||||
|
||||
|
||||
struct ContactsState {
|
||||
var saveErrorMessage: String?
|
||||
}
|
||||
|
||||
|
||||
|
||||
enum ContactsSideEffect: SideEffect {
|
||||
case save(_ contact: Contact)
|
||||
|
||||
|
||||
func mapToAction() -> AnyPublisher<AppAction, Never> {
|
||||
switch self {
|
||||
case .save(let contact):
|
||||
guard let context = contact.managedObjectContext else {
|
||||
return Just(AppAction.contacts(.saveErrorMessageSet("No managed object context found")))
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
return Just(context)
|
||||
.flatMap { context in
|
||||
CurrentApp.coreDataManager.save(context)
|
||||
.map { AppAction.contacts(.saveErrorMessageSet(nil)) }
|
||||
.catch { error in
|
||||
Just(AppAction.contacts(.saveErrorMessageSet(error.localizedDescription)))
|
||||
}
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
enum ContactsAction {
|
||||
case saveErrorMessageSet(String?)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Reducer
|
||||
let contactsReducer: Reducer<ContactsState, ContactsAction> = Reducer(
|
||||
reduce: { state, action in
|
||||
switch action {
|
||||
case .saveErrorMessageSet(let message):
|
||||
state.saveErrorMessage = message
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
@ -49,6 +49,8 @@
|
|||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>This app would like to scan QR codes in order to add to your collection of contacts.</string>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
//
|
||||
// QRCodeScannerView+Coordinator.swift
|
||||
// QRConnections
|
||||
//
|
||||
// Created by CypherPoet on 1/13/20.
|
||||
// ✌️
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AVFoundation
|
||||
|
||||
|
||||
// MARK: - Coordinator
|
||||
extension QRCodeScannerView {
|
||||
|
||||
class Coordinator: NSObject {
|
||||
let onScanCompleted: QRCodeScannerView.CompletionHandler
|
||||
let simulatedData: String
|
||||
|
||||
init(
|
||||
simulatedData: String,
|
||||
onScanCompleted: @escaping QRCodeScannerView.CompletionHandler
|
||||
) {
|
||||
self.simulatedData = simulatedData
|
||||
self.onScanCompleted = onScanCompleted
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ScannerViewControllerCaptureSessionDelegate
|
||||
extension QRCodeScannerView.Coordinator: ScannerViewControllerCaptureSessionDelegate {
|
||||
|
||||
func captureSession(didScan metadataObject: ScannerViewController.CapturedOutput) {
|
||||
#if targetEnvironment(simulator)
|
||||
onScanCompleted(.success(simulatedData))
|
||||
|
||||
#else
|
||||
|
||||
guard
|
||||
let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject,
|
||||
let stringValue = readableObject.stringValue else
|
||||
{ return }
|
||||
|
||||
AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
|
||||
|
||||
onScanCompleted(.success(stringValue))
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
func captureSessionWasUnableToAddInput() {
|
||||
onScanCompleted(.failure(.badInput))
|
||||
}
|
||||
|
||||
func captureSessionWasUnableToAddOutput() {
|
||||
onScanCompleted(.failure(.badOutput))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
//
|
||||
// QRCodeScannerView.swift
|
||||
// QRConnections
|
||||
//
|
||||
// Created by CypherPoet on 1/13/20.
|
||||
// ✌️
|
||||
//
|
||||
|
||||
|
||||
import SwiftUI
|
||||
import AVFoundation
|
||||
|
||||
|
||||
struct QRCodeScannerView {
|
||||
typealias UIViewControllerType = ScannerViewController
|
||||
typealias Completion = Result<String, ScanError>
|
||||
typealias CompletionHandler = ((Completion) -> Void)
|
||||
|
||||
var codeTypes: [AVMetadataObject.ObjectType] = [.qr]
|
||||
var simulatedData = ""
|
||||
let onScanCompleted: CompletionHandler
|
||||
}
|
||||
|
||||
|
||||
extension QRCodeScannerView {
|
||||
enum ScanError: Error {
|
||||
case badInput
|
||||
case badOutput
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UIViewControllerRepresentable
|
||||
extension QRCodeScannerView: UIViewControllerRepresentable {
|
||||
|
||||
func makeCoordinator() -> Self.Coordinator {
|
||||
Self.Coordinator(
|
||||
simulatedData: simulatedData,
|
||||
onScanCompleted: onScanCompleted
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
func makeUIViewController(
|
||||
context: UIViewControllerRepresentableContext<QRCodeScannerView>
|
||||
) -> UIViewControllerType {
|
||||
let viewController = ScannerViewController()
|
||||
|
||||
viewController.codeTypes = codeTypes
|
||||
viewController.captureSessionDelegate = context.coordinator
|
||||
|
||||
return viewController
|
||||
}
|
||||
|
||||
|
||||
func updateUIViewController(
|
||||
_ uiViewController: UIViewControllerType,
|
||||
context: UIViewControllerRepresentableContext<QRCodeScannerView>
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: - Preview
|
||||
struct QRCodeScannerView_Previews: PreviewProvider {
|
||||
|
||||
static var previews: some View {
|
||||
QRCodeScannerView(onScanCompleted: { _ in })
|
||||
}
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
//
|
||||
// ScannerViewControlller.swift
|
||||
// QRConnections
|
||||
//
|
||||
// Created by CypherPoet on 1/14/20.
|
||||
// ✌️
|
||||
//
|
||||
|
||||
import AVFoundation
|
||||
import UIKit
|
||||
|
||||
|
||||
protocol ScannerViewControllerCaptureSessionDelegate: class {
|
||||
func captureSession(didScan metadataObject: ScannerViewController.CapturedOutput)
|
||||
func captureSessionWasUnableToAddInput()
|
||||
func captureSessionWasUnableToAddOutput()
|
||||
}
|
||||
|
||||
|
||||
public final class ScannerViewController: UIViewController {
|
||||
private var captureSession: AVCaptureSession!
|
||||
private var previewLayer: AVCaptureVideoPreviewLayer!
|
||||
|
||||
var codeTypes: [AVMetadataObject.ObjectType] = [.qr]
|
||||
weak var captureSessionDelegate: ScannerViewControllerCaptureSessionDelegate?
|
||||
}
|
||||
|
||||
|
||||
extension ScannerViewController {
|
||||
#if targetEnvironment(simulator)
|
||||
typealias CapturedOutput = String
|
||||
#else
|
||||
typealias CapturedOutput = AVMetadataObject
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: - Computeds
|
||||
extension ScannerViewController {
|
||||
|
||||
override public var prefersStatusBarHidden: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
override public var supportedInterfaceOrientations: UIInterfaceOrientationMask {
|
||||
return .portrait
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - View Controller Lifecycle
|
||||
|
||||
#if targetEnvironment(simulator)
|
||||
|
||||
extension ScannerViewController {
|
||||
|
||||
override public func loadView() {
|
||||
view = UIView()
|
||||
|
||||
let label = UILabel()
|
||||
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
label.text = "You're running in the simulator, which means the camera isn't available. Tap anywhere to send back some simulated data."
|
||||
|
||||
view.addSubview(label)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
label.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
|
||||
label.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor),
|
||||
label.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
|
||||
label.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor)
|
||||
])
|
||||
}
|
||||
|
||||
|
||||
override public func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
captureSessionDelegate?.captureSession(didScan: "")
|
||||
dismiss(animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#else
|
||||
|
||||
|
||||
extension ScannerViewController {
|
||||
|
||||
override public func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
view.backgroundColor = UIColor.black
|
||||
captureSession = AVCaptureSession()
|
||||
|
||||
|
||||
guard let videoCaptureDevice = AVCaptureDevice.default(for: .video) else { return }
|
||||
guard let videoInput = try? AVCaptureDeviceInput(device: videoCaptureDevice) else { return }
|
||||
|
||||
|
||||
if (captureSession.canAddInput(videoInput)) {
|
||||
captureSession.addInput(videoInput)
|
||||
} else {
|
||||
captureSessionDelegate?.captureSessionWasUnableToAddInput()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
let metadataOutput = AVCaptureMetadataOutput()
|
||||
|
||||
if (captureSession.canAddOutput(metadataOutput)) {
|
||||
captureSession.addOutput(metadataOutput)
|
||||
|
||||
metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
|
||||
metadataOutput.metadataObjectTypes = codeTypes
|
||||
} else {
|
||||
captureSessionDelegate?.captureSessionWasUnableToAddOutput()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
|
||||
previewLayer.frame = view.layer.bounds
|
||||
previewLayer.videoGravity = .resizeAspectFill
|
||||
|
||||
view.layer.addSublayer(previewLayer)
|
||||
|
||||
captureSession.startRunning()
|
||||
}
|
||||
|
||||
|
||||
override public func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
if (captureSession?.isRunning == false) {
|
||||
captureSession.startRunning()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override public func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
|
||||
if (captureSession?.isRunning == true) {
|
||||
captureSession.stopRunning()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#if targetEnvironment(simulator)
|
||||
#else
|
||||
extension ScannerViewController: AVCaptureMetadataOutputObjectsDelegate {
|
||||
|
||||
public func metadataOutput(
|
||||
_ output: AVCaptureMetadataOutput,
|
||||
didOutput metadataObjects: [AVMetadataObject],
|
||||
from connection: AVCaptureConnection
|
||||
) {
|
||||
captureSession.stopRunning()
|
||||
|
||||
if let metadataObject = metadataObjects.first {
|
||||
captureSessionDelegate?.captureSession(didScan: metadataObject)
|
||||
}
|
||||
|
||||
dismiss(animated: true)
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -9,13 +9,17 @@
|
|||
import SwiftUI
|
||||
|
||||
|
||||
struct ContactsContainerView: View {
|
||||
struct ContactsContainerView {
|
||||
@EnvironmentObject private var store: AppStore
|
||||
|
||||
@ObservedObject var viewModel = ViewModel()
|
||||
|
||||
@State private var isShowingScannerView = false
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Body
|
||||
extension ContactsContainerView {
|
||||
// MARK: - View
|
||||
extension ContactsContainerView: View {
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
|
@ -27,6 +31,12 @@ extension ContactsContainerView {
|
|||
}
|
||||
.navigationBarTitle("Collected Contacts")
|
||||
.navigationBarItems(trailing: addContactButton)
|
||||
.sheet(isPresented: $isShowingScannerView) {
|
||||
QRCodeScannerView(
|
||||
simulatedData: "🚀 Rocket Man",
|
||||
onScanCompleted: self.codeScanCompleted(_:)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -56,7 +66,7 @@ extension ContactsContainerView {
|
|||
|
||||
private var addContactButton: some View {
|
||||
Button(action: {
|
||||
|
||||
self.isShowingScannerView = true
|
||||
}) {
|
||||
Image(systemName: "qrcode.viewfinder")
|
||||
.imageScale(.large)
|
||||
|
@ -66,6 +76,30 @@ extension ContactsContainerView {
|
|||
}
|
||||
|
||||
|
||||
// MARK: - Private Helpers
|
||||
private extension ContactsContainerView {
|
||||
|
||||
func codeScanCompleted(_ result: QRCodeScannerView.Completion) {
|
||||
switch result {
|
||||
case .success(let qrCodeString):
|
||||
createNewContact(from: qrCodeString)
|
||||
// self.store.send(ContactsSideEffect.createContact(qrCodeString))
|
||||
case .failure(let error):
|
||||
print("Scanning failed. Error \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func createNewContact(from qrCodeString: String) {
|
||||
let context = CurrentApp.coreDataManager.backgroundContext
|
||||
let contact = Contact.make(fromName: qrCodeString, using: context)
|
||||
|
||||
self.store.send(ContactsSideEffect.save(contact))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: - Preview
|
||||
struct ContactsContainerView_Previews: PreviewProvider {
|
||||
|
||||
|
@ -74,5 +108,6 @@ struct ContactsContainerView_Previews: PreviewProvider {
|
|||
viewModel: .init()
|
||||
)
|
||||
.environment(\.managedObjectContext, CurrentApp.coreDataManager.mainContext)
|
||||
.environmentObject(SampleData.appStore)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,6 @@ struct RootView: View {
|
|||
extension RootView {
|
||||
|
||||
var body: some View {
|
||||
|
||||
TabView {
|
||||
ContactsContainerView(viewModel: .init())
|
||||
.tabItem {
|
||||
|
@ -42,7 +41,6 @@ extension RootView {
|
|||
}
|
||||
.tag(Tab.userQRCode)
|
||||
}
|
||||
// .environmentObject(store)
|
||||
.edgesIgnoringSafeArea(.top)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue