I have the following ARView:
import SwiftUI
import UIKit
import RealityKit
import ARKit
struct ARViewContainer: UIViewRepresentable {
#EnvironmentObject var selectedFood: SelectedFood
#EnvironmentObject var arSession: ARSessionObservable
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> ARView {
let arView = ARView(frame: .zero)
let config = ARWorldTrackingConfiguration()
config.planeDetection = [.vertical, .horizontal]
config.environmentTexturing = .automatic
if ARWorldTrackingConfiguration.supportsSceneReconstruction(.mesh) {
config.sceneReconstruction = .mesh
}
arView.session.delegate = context.coordinator
arView.session.run(config)
arSession.session = arView.session
return arView
}
func updateUIView(_ uiView: ARView, context: Context) {
if (!selectedFood.food.image.isEmpty) {
let data = try! Data(contentsOf: URL(string: self.selectedFood.food.image)!)
let fileURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
try! data.write(to: fileURL)
do {
let texture = try TextureResource.load(contentsOf: fileURL)
var material = SimpleMaterial()
material.baseColor = MaterialColorParameter.texture(texture)
material.tintColor = UIColor.white.withAlphaComponent(0.99)
let entity = ModelEntity(mesh: .generatePlane(width: 0.1, height: 0.1), materials: [material])
let anchor = AnchorEntity(.plane(.any, classification: .any, minimumBounds: .zero))
anchor.addChild(entity)
uiView.scene.addAnchor(anchor)
} catch {
print(error.localizedDescription)
}
}
}
class Coordinator: NSObject, ARSessionDelegate, ARSCNViewDelegate {
var arVC: ARViewContainer
init(_ arViewContainer: ARViewContainer) {
self.arVC = arViewContainer
}
func session(_ session: ARSession, didUpdate frame: ARFrame) {
}
func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
}
}
}
And in HomeView i have the following two variables:
#StateObject var arSession: ARSessionObservable = ARSessionObservable()
#State private var capturedImage: UIImage = UIImage()
The following button with action:
Button {
if let capturedFrame = arSession.session.currentFrame {
let ciimg = CIImage(cvPixelBuffer: capturedFrame.capturedImage)
if let cgImage = convertCIImageToCGImage(inputImage: ciimg) {
capturedImage = UIImage(cgImage: cgImage).rotate(radians: .pi / 2)
self.isShowingMail = true
}
}
} label: {
Image("ShareScreen")
.resizable()
.aspectRatio(contentMode:.fit)
.frame(width: 66, height: 66, alignment: .center)
}
Which takes the currentFrame from the session and opens a Mail sharing model with attachment:
.sheet(isPresented: $isShowingMail) {
MailComposeViewController(toRecipients: [], mailBody: nil, imageAttachment: capturedImage) {
self.isShowingMail = false
}
The mail sharing:
func makeUIViewController(context: UIViewControllerRepresentableContext<MailComposeViewController>) -> MFMailComposeViewController {
let mail = MFMailComposeViewController()
mail.mailComposeDelegate = context.coordinator
mail.setToRecipients(self.toRecipients)
if let body = mailBody {
mail.setMessageBody(body, isHTML: true)
}
if let image = imageAttachment {
if let imageData = image.pngData() {
mail.addAttachmentData(imageData, mimeType: "image/png", fileName: "image.png")
}
}
return mail
}
The problem is that on the preview there are present the Model Entities, photo below:
And when i press share, on the mail preview the model is missing from the frame:
I managed to make it work by moving arView: ARView! outside the ARViewContainer
var arView: ARView!
struct ARViewContainer: UIViewRepresentable {
func makeUIView(context: Context) -> ARView {
arView = ARView(frame: .zero)
let config = ARWorldTrackingConfiguration()
config.planeDetection = [.vertical, .horizontal]
config.environmentTexturing = .automatic
if ARWorldTrackingConfiguration.supportsSceneReconstruction(.mesh) {
config.sceneReconstruction = .mesh
}
arView.session.delegate = context.coordinator
arView.session.run(config)
return arView
}
}
And then calling the snapshot function to arView in the other View:
Button {
arView.snapshot(saveToHDR: false) { image in
let image = UIImage(data: (image?.pngData())!)
capturedImage = image!
self.isShowingMail = true
}
Related
I have a ball and a plane (floor) in realityKit.
The ball falls and has to collide on the plane. But the ball is falling through the plane. When the ball hits the plane i want a print statement "OCCURED" but this is also not happening.
My code:
struct ARViewContainer: UIViewRepresentable {
let arView = ARView(frame: .zero)
func makeUIView(context: Context) -> ARView {
var subscriptions: [Cancellable] = []
let anchor = AnchorEntity()
//generate a plane
let planeModel = ModelEntity(mesh: .generatePlane(width: 2.0,
depth: 2.0))
as (Entity & HasCollision)
planeModel.position = [0,-10,-1.5]
planeModel.generateCollisionShapes(recursive: true)
//generate a ball
let ballModel = ModelEntity(mesh: .generateSphere(radius: 0.4))
as (Entity & HasCollision & HasPhysicsBody)
ballModel.generateCollisionShapes(recursive: true)
ballModel.position = [0,0, -1.5]
ballModel.physicsBody = .init()
ballModel.physicsBody?.mode = .dynamic
let sub = arView.scene.subscribe(to: CollisionEvents.Began.self,
on: ballModel) { _ in print("OCCURED!") }
subscriptions.append(sub)
anchor.addChild(ballModel)
anchor.addChild(planeModel)
arView.scene.addAnchor(anchor)
return arView
}
}
Collider and collidee
This code activates physics and prints "occured" N times, thanks to CollisionEvents' subscription:
import SwiftUI
import RealityKit
import Combine
struct ARViewContainer: UIViewRepresentable {
#State var subscriptions: [AnyCancellable] = []
let arView = ARView(frame: .zero)
let anchor = AnchorEntity()
func makeUIView(context: Context) -> ARView {
// PLANE
let planeModel = ModelEntity(mesh: .generatePlane(width: 2.0,
depth: 2.0))
as (Entity & HasPhysics)
planeModel.position = [0,-1,-1.5]
planeModel.generateCollisionShapes(recursive: false)
planeModel.physicsBody = .init()
planeModel.physicsBody?.mode = .static
// BALL
let ballModel = ModelEntity(mesh: .generateSphere(radius: 0.2),
materials: [UnlitMaterial()])
as (Entity & HasPhysics)
ballModel.position = [0, 1,-1.5]
ballModel.generateCollisionShapes(recursive: false)
ballModel.physicsBody = .init()
ballModel.physicsBody?.mode = .dynamic
DispatchQueue.main.async {
arView.scene.subscribe(to: CollisionEvents.Began.self,
on: ballModel) { _ in
print("OCCURED!")
}.store(in: &subscriptions)
}
anchor.addChild(ballModel)
anchor.addChild(planeModel)
arView.scene.addAnchor(anchor)
return arView
}
func updateUIView(_ view: ARView, context: Context) { }
}
struct ContentView : View {
var body: some View {
ARViewContainer().ignoresSafeArea()
}
}
Thanks to this answer i can see QuickLook preview of a pdf embedded in my swiftui view.
But it's only to preview the pdf that's stored in the app bundle resource. How do i use NSOpenPanel to choose a file and display it in the QLPreviewView?
import SwiftUI
import AppKit
import Quartz
func loadPreviewItem(with name: String) -> NSURL {
let file = name.components(separatedBy: ".")
let path = Bundle.main.path(forResource: file.first!, ofType: file.last!)
let url = NSURL(fileURLWithPath: path ?? "")
print(url)
return url
}
struct MyPreview: NSViewRepresentable {
var fileName: String
func makeNSView(context: NSViewRepresentableContext<MyPreview>) -> QLPreviewView {
let preview = QLPreviewView(frame: .zero, style: .normal)
preview?.autostarts = true
preview?.previewItem = loadPreviewItem(with: fileName) as QLPreviewItem
return preview ?? QLPreviewView()
}
func updateNSView(_ nsView: QLPreviewView, context: NSViewRepresentableContext<MyPreview>) {
}
typealias NSViewType = QLPreviewView
}
struct ContentView: View {
var body: some View {
// example.pdf is expected in app bundle resources
VStack {
MyPreview(fileName: "testing.pdf")
Divider()
}
Button("Select PDF") {
let openPanel = NSOpenPanel()
openPanel.allowedFileTypes = ["pdf"]
openPanel.allowsMultipleSelection = false
openPanel.canChooseDirectories = false
openPanel.canChooseFiles = true
openPanel.runModal()
}
}
}
*UPDATE
This is what i've tried but nothing happens after i choose a pdf file with the openpanel. The View is blank. I think there's something that i haven't done correctly in the updateNSView.
struct ContentView: View {
#State var filename = ""
var body: some View {
VStack {
MyPreview(fileName: filename)
Divider()
}
Button("Select PDF") {
let openPanel = NSOpenPanel()
openPanel.allowedFileTypes = ["pdf"]
openPanel.allowsMultipleSelection = false
openPanel.canChooseDirectories = false
openPanel.canChooseFiles = true
openPanel.runModal()
print(openPanel.url!.lastPathComponent)
filename = openPanel.url!.lastPathComponent
//
}
}
}
struct MyPreview: NSViewRepresentable {
var fileName: String
func makeNSView(context: NSViewRepresentableContext<MyPreview>) -> QLPreviewView {
let preview = QLPreviewView(frame: .zero, style: .normal)
preview?.autostarts = true
preview?.previewItem = loadPreviewItem(with: fileName) as QLPreviewItem
return preview ?? QLPreviewView()
}
func updateNSView(_ nsView: QLPreviewView, context: NSViewRepresentableContext<MyPreview>) {
let preview = QLPreviewView(frame: .zero, style: .normal)
preview?.refreshPreviewItem()
}
typealias NSViewType = QLPreviewView
}
*UPDATE 2
My latest attempt using now a model to update the file url after choosing one with the open panel but nothing still happens.
It updates pdfurl successfully with the file url but the QLPreviewView doesn't update with the changes in updateNSView. I'm using the refreshItemPreview() which should work but i'm not sure what i'm doing wrong here
class PDFViewModel: ObservableObject {
#Published var pdfurl = ""
}
struct MyPreview: NSViewRepresentable {
#ObservedObject var pdfVM = PDFViewModel()
func makeNSView(context: NSViewRepresentableContext<MyPreview>) -> QLPreviewView {
let preview = QLPreviewView(frame: .zero, style: .normal)
preview?.previewItem = NSURL(string: pdfVM.pdfurl)
return preview ?? QLPreviewView()
}
func updateNSView(_ nsView: QLPreviewView, context: NSViewRepresentableContext<MyPreview>) {
let preview = QLPreviewView(frame: .zero, style: .normal)
preview?.refreshPreviewItem()
}
typealias NSViewType = QLPreviewView
}
struct ContentView: View {
#ObservedObject var pdfVM = PDFViewModel()
var body: some View {
VStack {
MyPreview()
Divider()
}
Button("Select PDF") {
let openPanel = NSOpenPanel()
openPanel.allowedFileTypes = ["pdf"]
openPanel.allowsMultipleSelection = false
openPanel.canChooseDirectories = false
openPanel.canChooseFiles = true
openPanel.runModal()
pdfVM.pdfurl = "\(openPanel.url!)"
print("the url is now \(pdfVM.pdfurl)")
}
}
}
The answer is quick direct. NSOpenPanel is subclassed from NSSavePanel. You can find want you need there.
let response = openPanel.runModal()
if response == .OK, let fileURL = openPanel.url {
// read your file here
}
Problem
It looks like you are not getting the file correctly - currently, you are only getting the last part of the URL. You should get the full URL if you are not loading from Bundle.main.
Solution
For example, this function will get you a full URL from an item.
func openPanelURL() -> URL?{
let openPanel = NSOpenPanel()
openPanel.allowedFileTypes = ["pdf"]
openPanel.allowsMultipleSelection = false
openPanel.canChooseDirectories = false
openPanel.canChooseFiles = true
openPanel.runModal()
return response == .OK ? openPanel.url : nil
}
Source: https://serialcoder.dev/text-tutorials/macos-tutorials/save-and-open-panels-in-swiftui-based-macos-apps/
Then, you can just pass in your PreviewItemURL into the NSViewRepresentable, like so.
struct MyPreview: NSViewRepresentable {
typealias NSViewType = QLPreviewView
var fileName: String
func makeNSView(context: NSViewRepresentableContext<MyPreview>) -> QLPreviewView {
let preview = QLPreviewView(frame: .zero, style: .normal)
preview?.autostarts = true
preview?.previewItemURL = NSURL(string: filename)!
// WARNING! I am force unwrapping above using the !, this is unsafe for a production app
//As it can cause the app to crash. Instead, optionally unwrap using the ? and ?? operators
return preview ?? QLPreviewView()
}
func updateNSView(_ nsView: QLPreviewView, context: NSViewRepresentableContext<MyPreview>) {
let preview = QLPreviewView(frame: .zero, style: .normal)
preview?.refreshPreviewItem()
}
}
Docs
Docs for the NSURL init: https://developer.apple.com/documentation/foundation/nsurl/1414650-fileurl
And for previewItemURL: https://developer.apple.com/documentation/quicklook/qlpreviewitem/1419913-previewitemurl
Warning
I am force unwrapping in the code above - this will crash if you have a nil or incorrect value. In a production app, I would suggest using optional unwrapping. See an article from Hacking With Swift here: https://www.hackingwithswift.com/quick-start/understanding-swift/when-should-you-force-unwrap-optionals-in-swift.
Hacky solution but it works....
I used a looped timer that monitors and sets the preview item url which is also now stored with AppStorage
class PDFViewModel: ObservableObject {
#AppStorage("pdfurl") var pdfurl: String = ""
}
struct MyPreview: NSViewRepresentable {
#ObservedObject var pdfVM = PDFViewModel()
let preview = QLPreviewView(frame: .zero, style: .normal)
func makeNSView(context: NSViewRepresentableContext<MyPreview>) -> QLPreviewView {
preview?.autostarts = true
preview?.shouldCloseWithWindow = false
preview?.previewItem = NSURL(string: pdfVM.pdfurl)
updatedocument()
return preview ?? QLPreviewView()
}
func updatedocument() {
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { _ in
let delay = DispatchTime.now() + 0.3
DispatchQueue.main.asyncAfter(deadline: delay, execute: {
preview?.previewItem = NSURL(string: pdfVM.pdfurl)
})
}
}
func updateNSView(_ nsView: QLPreviewView, context: NSViewRepresentableContext<MyPreview>) {
}
typealias NSViewType = QLPreviewView
}
struct ContentView: View {
#ObservedObject var pdfVM = PDFViewModel()
var body: some View {
VStack {
MyPreview()
Divider()
}
Button("Select Document") {
let openPanel = NSOpenPanel()
openPanel.allowedFileTypes = ["pdf", "jpg","doc","pptx", "png", "xls", "xlsx", "docx", "jpeg", "txt"]
openPanel.allowsMultipleSelection = false
openPanel.canChooseDirectories = false
openPanel.canChooseFiles = true
openPanel.begin { (result) in
if result == NSApplication.ModalResponse.OK {
if let url = openPanel.url {
pdfVM.pdfurl = "\(url)"
}
} else if result == NSApplication.ModalResponse.cancel {
}
}
}
}
}
I am making an app pdf reader using PDFKit but I am unable to get the current page number. I can get the total pages by pdfView.document?.pageCount. The alternative way we can use for this is to change the page by button and count it but I want the PDFView default feature Changing the page by Swipe by sitting the pdfView.usePageViewController(true) but it does not give any method to get the current page number
Code
struct ContentView: View {
let url = Bundle.main.url(forResource: "file", withExtension: "pdf")
var body: some View {
VStack{
PDFKitRepresentedView(data: try! Data(contentsOf: url!))
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
import PDFKit
import SwiftUI
struct PDFKitRepresentedView: UIViewRepresentable {
typealias UIViewType = PDFView
let data: Data
func makeUIView(context _: UIViewRepresentableContext<PDFKitRepresentedView>) -> UIViewType {
// Create a `PDFView` and set its `PDFDocument`.
let pdfView = PDFView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
pdfView.document = PDFDocument(data: data)
pdfView.backgroundColor = UIColor.red
pdfView.displayMode = .singlePage
pdfView.displayDirection = .horizontal
pdfView.usePageViewController(true)
pdfView.maxScaleFactor = pdfView.scaleFactorForSizeToFit
pdfView.minScaleFactor = pdfView.scaleFactorForSizeToFit
pdfView.autoScales = true
return pdfView
}
func updateUIView(_ pdfView: UIViewType, context _: UIViewRepresentableContext<PDFKitRepresentedView>) {
pdfView.document = PDFDocument(data: data)
}
}
Update
According to suggestion given by
workingdog support Ukraine below the coordinator class printing the result but when I use Binding to pass the currentPage to SwiftUI its not working the page number is not updating in UI and on swiping its repeating the first two pages only
New Updated code
struct ContentView: View {
#State var currentPage = -1
#State var totalPages :Int?
let url = Bundle.main.url(forResource: "file", withExtension: "pdf")
var body: some View {
VStack{
HStack{
Text("\(currentPage)/")
Text("\(totalPages ?? 0)")
}
if let url = url {
PDFKitRepresentedView(data:try! Data(contentsOf: url),totalPages: $totalPages,currentPage: $currentPage)
}
}
}
}
struct PDFKitRepresentedView: UIViewRepresentable {
typealias UIViewType = PDFView
let data: Data
#Binding var totalPages:Int?
#Binding var currentPage :Int
let pdfView = PDFView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
func makeUIView(context: Context) -> UIViewType {
pdfView.document = PDFDocument(data: data)
pdfView.backgroundColor = UIColor.red
pdfView.displayMode = .singlePage
pdfView.displayDirection = .horizontal
pdfView.usePageViewController(true)
pdfView.maxScaleFactor = pdfView.scaleFactorForSizeToFit
pdfView.minScaleFactor = pdfView.scaleFactorForSizeToFit
pdfView.autoScales = true
pdfView.delegate = context.coordinator
return pdfView
}
func updateUIView(_ pdfView: UIViewType, context _: Context) {
pdfView.document = PDFDocument(data: data)
DispatchQueue.main.async {
totalPages = pdfView.document?.pageCount
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(self, cp: $currentPage)
}
class Coordinator: NSObject, PDFViewDelegate {
var parent: PDFKitRepresentedView
#Binding var currentPage :Int
init(_ parent: PDFKitRepresentedView,cp:Binding<Int>) {
self.parent = parent
_currentPage = cp
super.init()
NotificationCenter.default.addObserver(self, selector: #selector(pageChangeHandler(_:)), name: .PDFViewPageChanged, object: nil)
}
#objc func pageChangeHandler(_ notification: Notification) {
if let thePage = parent.pdfView.currentPage,
let ndx = parent.pdfView.document?.index(for: thePage),
currentPage != ndx {
DispatchQueue.main.async {
self.currentPage = ndx
}
print("--------> currentPageIndex: \(ndx) ")
}
}
}
}
According to the docs at: https://developer.apple.com/documentation/pdfkit/pdfview there is a currentPage that returns the current page. You could then use something like this to get the index of it:
if let thePage = pdfView.currentPage, let ndx = pdfView.document?.index(for: thePage) {
print("--> currentPageIndex: \(ndx) ")
// ....
}
EDIT-1:
Try the following approach, using a Coordinator class for the PDFViewDelegate and getting notified when a .PDFViewPageChanged with a NotificationCenter.default.addObserver(...).
You will have to adjust the code for your own purpose.
struct PDFKitRepresentedView: UIViewRepresentable {
typealias UIViewType = PDFView
let data: Data
let pdfView = PDFView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
func makeUIView(context: Context) -> UIViewType {
pdfView.document = PDFDocument(data: data)
pdfView.backgroundColor = UIColor.red
pdfView.displayMode = .singlePage
pdfView.displayDirection = .horizontal
pdfView.usePageViewController(true)
pdfView.maxScaleFactor = pdfView.scaleFactorForSizeToFit
pdfView.minScaleFactor = pdfView.scaleFactorForSizeToFit
pdfView.autoScales = true
pdfView.delegate = context.coordinator
return pdfView
}
func updateUIView(_ pdfView: UIViewType, context _: Context) {
pdfView.document = PDFDocument(data: data)
}
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
class Coordinator: NSObject, PDFViewDelegate {
var parent: PDFKitRepresentedView
var prevPage = -1
init(_ parent: PDFKitRepresentedView) {
self.parent = parent
super.init()
NotificationCenter.default.addObserver(self, selector: #selector(pageChangeHandler(_:)), name: .PDFViewPageChanged, object: nil)
}
#objc func pageChangeHandler(_ notification: Notification) {
if let thePage = parent.pdfView.currentPage,
let ndx = parent.pdfView.document?.index(for: thePage),
prevPage != ndx {
print("--------> currentPageIndex: \(ndx) ")
prevPage = ndx
}
}
}
}
EDIT-2:
To access the currentPage in ContentView, that is, outside the PDFViewer,
you can use the following approach.
It uses a .onReceive(...) of a page change notification, and some minor changes of
the original code.
struct ContentView: View {
#State var currentPage = 0
let pdfViewer: PDFViewer // <--- here
init() {
if let url = Bundle.main.url(forResource: "file", withExtension: "pdf"),
let docData = try? Data(contentsOf: url) {
self.pdfViewer = PDFViewer(data: docData)
} else {
self.pdfViewer = PDFViewer(data: Data())
}
}
var body: some View {
VStack {
Text("page \(currentPage)")
pdfViewer
.onReceive(NotificationCenter.default.publisher(for: .PDFViewPageChanged)) { _ in
if let thePage = pdfViewer.pdfView.currentPage,
let ndx = pdfViewer.pdfView.document?.index(for: thePage), currentPage != ndx {
currentPage = ndx
}
}
}
}
}
struct PDFViewer: UIViewRepresentable {
typealias UIViewType = PDFView
let data: Data
let pdfView = PDFView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
func makeUIView(context: Context) -> UIViewType {
pdfView.document = PDFDocument(data: data)
pdfView.backgroundColor = UIColor.red
pdfView.displayMode = .singlePage
pdfView.displayDirection = .horizontal
pdfView.usePageViewController(true)
pdfView.maxScaleFactor = pdfView.scaleFactorForSizeToFit
pdfView.minScaleFactor = pdfView.scaleFactorForSizeToFit
pdfView.autoScales = true
return pdfView
}
func updateUIView(_ pdfView: UIViewType, context _: Context) {
pdfView.document = PDFDocument(data: data)
}
}
I want to detect QR codes in the vertical plane and place a node on top of the detected QR. For QR detection, I used Vision framework and Arkit to place the nodes as below code. Whenever placing the node, it is not attached to the QR code and placed somewhere else.
Could someone help to figure out what I have done wrong?
class ViewController: UIViewController, ARSCNViewDelegate,ARSessionDelegate{
#IBOutlet var sceneView: ARSCNView!
var qrRequests = [VNRequest]()
var detectedDataAnchor: ARAnchor?
var processing = false
let configuration = ARWorldTrackingConfiguration()
override func viewDidLoad() {
super.viewDidLoad()
self.sceneView.delegate = self
self.sceneView.session.delegate = self
self.sceneView.session.run(configuration)
startQrCodeDetection()
}
func startQrCodeDetection() {
let request = VNDetectBarcodesRequest(completionHandler: self.requestHandler)
self.qrRequests = [request]
}
public func session(_ session: ARSession, didUpdate frame: ARFrame) {
DispatchQueue.global(qos: .userInitiated).async {
do {
if self.processing {
return
}
self.processing = true
let imageRequestHandler = VNImageRequestHandler(cvPixelBuffer: frame.capturedImage,
options: [:])
try imageRequestHandler.perform(self.qrRequests)
} catch {
}
}
}
func requestHandler(request: VNRequest, error: Error?) {
if let results = request.results, let result = results.first as? VNBarcodeObservation {
guard let payload = result.payloadStringValue else {return}
var rect = result.boundingBox
let center = CGPoint(x: rect.midX, y: rect.midY)
DispatchQueue.main.async {
self.hitTestQrCode(center: center)
self.processing = false
}
} else {
self.processing = false
}
}
func hitTestQrCode(center: CGPoint) {
print("Hit Test")
if let hitTestResults = self.sceneView?.hitTest(center, types: [.featurePoint, .existingPlaneUsingExtent] ),
let hitTestResult = hitTestResults.first {
if let detectedDataAnchor = self.detectedDataAnchor,
let node = self.sceneView.node(for: detectedDataAnchor) {
node.transform = SCNMatrix4(hitTestResult.worldTransform)
} else {
self.detectedDataAnchor = ARAnchor(transform: hitTestResult.worldTransform)
self.sceneView.session.add(anchor: self.detectedDataAnchor!)
}
}
}
func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
if self.detectedDataAnchor?.identifier == anchor.identifier {
let sphere = SCNSphere(radius: 0.02)
sphere.firstMaterial?.diffuse.contents = UIColor.red
let sphereNode = SCNNode(geometry: sphere)
sphereNode.transform = SCNMatrix4(anchor.transform)
return sphereNode
}
return nil
}
}
I build a StreamingView like this:
struct StreamingView: UIViewRepresentable {
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<StreamingView>) {
//
}
func makeUIView(context: UIViewRepresentableContext<StreamingView>) -> UIView {
let view = UIView()
let captureSession = AVCaptureSession()
captureSession.sessionPreset = .photo
guard let captureDevice = AVCaptureDevice.default(for: .video) else { return view}
guard let input = try? AVCaptureDeviceInput(device: captureDevice) else { return view}
captureSession.addInput(input)
captureSession.startRunning()
let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
view.layer.addSublayer(previewLayer)
previewLayer.frame = view.frame
return view
}
}
but It didn't work. how could I build a pure swiftui view for streaming?
Try the below demo code
Note: make sure all preparations done, like turned on Camera in capabilities, added NSCameraUsageDescription in Info.plist... and camera can be tested only on a real device.
import SwiftUI
import UIKit
import AVFoundation
class PreviewView: UIView {
private var captureSession: AVCaptureSession?
init() {
super.init(frame: .zero)
var allowedAccess = false
let blocker = DispatchGroup()
blocker.enter()
AVCaptureDevice.requestAccess(for: .video) { flag in
allowedAccess = flag
blocker.leave()
}
blocker.wait()
if !allowedAccess {
print("!!! NO ACCESS TO CAMERA")
return
}
// setup session
let session = AVCaptureSession()
session.beginConfiguration()
let videoDevice = AVCaptureDevice.default(.builtInWideAngleCamera,
for: .video, position: .unspecified) //alternate AVCaptureDevice.default(for: .video)
guard videoDevice != nil, let videoDeviceInput = try? AVCaptureDeviceInput(device: videoDevice!), session.canAddInput(videoDeviceInput) else {
print("!!! NO CAMERA DETECTED")
return
}
session.addInput(videoDeviceInput)
session.commitConfiguration()
self.captureSession = session
}
override class var layerClass: AnyClass {
AVCaptureVideoPreviewLayer.self
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var videoPreviewLayer: AVCaptureVideoPreviewLayer {
return layer as! AVCaptureVideoPreviewLayer
}
override func didMoveToSuperview() {
super.didMoveToSuperview()
if nil != self.superview {
self.videoPreviewLayer.session = self.captureSession
self.videoPreviewLayer.videoGravity = .resizeAspect
self.captureSession?.startRunning()
} else {
self.captureSession?.stopRunning()
}
}
}
struct PreviewHolder: UIViewRepresentable {
func makeUIView(context: UIViewRepresentableContext<PreviewHolder>) -> PreviewView {
PreviewView()
}
func updateUIView(_ uiView: PreviewView, context: UIViewRepresentableContext<PreviewHolder>) {
}
typealias UIViewType = PreviewView
}
struct DemoVideoStreaming: View {
var body: some View {
VStack {
PreviewHolder()
}.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
}
}