Swift UI and PDFKit - How do I update my page programmatically? - swiftui

I want to update the view when the State of currentpage changes by a button.
In console the tap make current page increase accordingly.
I tried with gotoNextPage() (SOLUTION 2)and it is not working
and also tried with go to method.(SOLUTION 1)
Non of them are updating even if printed values seems to be ok.
PDFVIEW CurrentPage value is:Optional(<PDFPage: 0x281917b60> page index 203)
Any solution, please?
#State var pdfName: String
#State var start: Int
PDFKitView(url: Bundle.main.url(forResource: pdfName, withExtension: "pdf")!, currentPage: start)
import SwiftUI
import PDFKit
struct PDFKitView: View {
var url: URL
var currentPage: Int
var body: some View {
PDFKitRepresentedView(url, currentPage)
}
}
struct PDFKitRepresentedView: UIViewRepresentable {
let url: URL
let currentPage: Int
let pdfView = PDFView()
init(_ url: URL, _ currentPage: Int) {
self.url = url
self.currentPage = currentPage
}
func makeUIView(context: UIViewRepresentableContext<PDFKitRepresentedView>) -> PDFKitRepresentedView.UIViewType {
print("PDFVIEW IS CREATED")
pdfView.document = PDFDocument(url: self.url)
pdfView.displayMode = .singlePage
pdfView.displayDirection = .horizontal
pdfView.autoScales = true
pdfView.usePageViewController(true)
pdfView.go(to: pdfView.document!.page(at: currentPage)!)
let total = pdfView.document?.pageCount
print("Total pages: \(total ?? 0)")
return pdfView
}
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<PDFKitRepresentedView>) {
// Update the view.
//THIS IS PRINTED IN CONSOLE EVERY TIME
print("PDFVIEW IS UPDATED")
print("CurrentPage value is:\(currentPage)")
//SOLUTION TRIED 1. SAME CODE WORKS ON makeUIView: I can see the pdf, scroll it and zoom. Also let it start at any page I want to. But not here.
print("PDFVIEW IS UPDATED")
print("CurrentPage value is:\(currentPage)")
//SOLUTION 1
pdfView.document = PDFDocument(url: self.url)
pdfView.displayMode = .singlePage
pdfView.displayDirection = .horizontal
pdfView.autoScales = true
pdfView.usePageViewController(true)
pdfView.go(to: pdfView.document!.page(at: currentPage)!)
print("PDFVIEW CurrentPage value is:\(pdfView.currentPage)")
/*
PDFVIEW IS UPDATED
CurrentPage value is:203
PDFVIEW CurrentPage value is:Optional(<PDFPage: 0x281917b60> page index 203)
*/
//SOLUTION TRIED 2
//goToNextPage()
}
func goToNextPage(){
pdfView.goToNextPage(nil)
}
}

The source of truth should be somewhere outside and injected by binding or other observable type inside.
Here is simplified fixed variant. Tested with Xcode 13.4 / iOS 15.5
Main part:
func makeUIView(context: Context) -> UIView {
guard let document = PDFDocument(url: self.url) else { return UIView() }
let pdfView = PDFView()
print("PDFVIEW IS CREATED")
pdfView.document = document
pdfView.displayMode = .singlePage
pdfView.displayDirection = .horizontal
pdfView.autoScales = true
pdfView.usePageViewController(true)
DispatchQueue.main.async {
self.total = document.pageCount
print("Total pages: \(total)")
}
return pdfView
}
func updateUIView(_ uiView: UIView, context: Context) {
guard let pdfView = uiView as? PDFView else { return }
if currentPage < total {
pdfView.go(to: pdfView.document!.page(at: currentPage)!)
}
}
Note: handling PDFView delegate navigation by swipe to pass back in binding is on you.
Complete test module is here

Related

SwiftUI - PDFkit - change PDF viewer background color?

I'm building an app that displays PDF files with SwiftUI and PDFkit.
Around the PDF file is the color gray with light theme and black with dark theme.
Is it possible to change the background color from secondary to another color or image?
If yes, how would I do that?
This is my code:
import SwiftUI
import PDFKit
struct MapPDFView: View {
var url: URL
var body: some View {
ZStack {
Image("Transparant")
.resizable()
.ignoresSafeArea()
MapPDFView1(url)
}
}
}
struct MapPDFView1: UIViewRepresentable {
let url: URL
init(_ url: URL) {
self.url = url
}
func makeUIView(context: UIViewRepresentableContext<MapPDFView1>) -> MapPDFView1.UIViewType {
let pdfView = PDFView()
pdfView.document = PDFDocument(url: self.url)
pdfView.autoScales = true
return pdfView
}
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<MapPDFView1>) {
}
}
struct MapPDFView_Previews: PreviewProvider {
static var previews: some View {
MapPDFView(url: Bundle.main.url(forResource: "Map", withExtension: "pdf")!
);}
}
The PDFView is a UIView, so just set background color to which ever needed, like
func makeUIView(context: UIViewRepresentableContext<MapPDFView1>) -> MapPDFView1.UIViewType {
let pdfView = PDFView()
pdfView.backgroundColor = .clear // << here !!

ZoomableScrollView no longer works in SwiftUI

Using Swift 5.5, Xcode 13.0, iOS 15.0.1,
Since Apple does not a good job on ScrollViews for SwiftUI (yet?), I had to implement my own zoomable ScrollView. See code below.
I had it successfully running for iOS13 and iOS14 - but now updating to iOS 15.0.1 I had to realise that it no longer works !
The error message is as follows:
objc[10882]: Cannot form weak reference to instance (0x1078d5540) of class _TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_5ImageVS_18_AspectRatioLayout__. It is possible that this object was over-released, or is in the process of deallocation.
dyld4 config: DYLD_LIBRARY_PATH=/usr/lib/system/introspection DYLD_INSERT_LIBRARIES=/Developer/usr/lib/libBacktraceRecording.dylib:/Developer/usr/lib/libMainThreadChecker.dylib:/Developer/Library/PrivateFrameworks/DTDDISupport.framework/libViewDebuggerSupport.dylib
(lldb)
I have everything inside a SwiftUIPager, and the problem occurs on the very last page when swiping. However, I don't think it is the Pagers fault since I can replace the ZoomableScrolView by a normal ImageView and everything works. Therefore I think Apple messed something up in their UIViewRepresentable. But maybe you can tell me more ??
Here is the entire code of my ZoomableScrolView:
import SwiftUI
struct ZoomableScrollView<Content: View>: UIViewRepresentable {
#Binding var didZoom: Bool
private var content: Content
init(didZoom: Binding<Bool>, #ViewBuilder content: () -> Content) {
_didZoom = didZoom
self.content = content()
}
func makeUIView(context: Context) -> UIScrollView {
// set up the UIScrollView
let scrollView = UIScrollView()
scrollView.delegate = context.coordinator // for viewForZooming(in:)
scrollView.maximumZoomScale = 20
scrollView.minimumZoomScale = 1
scrollView.bouncesZoom = true
// create a UIHostingController to hold our SwiftUI content
let hostedView = context.coordinator.hostingController.view!
hostedView.translatesAutoresizingMaskIntoConstraints = true
hostedView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
hostedView.frame = scrollView.bounds
hostedView.backgroundColor = .black
scrollView.addSubview(hostedView)
return scrollView
}
func makeCoordinator() -> Coordinator {
return Coordinator(hostingController: UIHostingController(rootView: self.content), didZoom: $didZoom)
}
func updateUIView(_ uiView: UIScrollView, context: Context) {
// update the hosting controller's SwiftUI content
context.coordinator.hostingController.rootView = self.content
assert(context.coordinator.hostingController.view.superview == uiView)
}
// MARK: - Coordinator
class Coordinator: NSObject, UIScrollViewDelegate {
var hostingController: UIHostingController<Content>
#Binding var didZoom: Bool
init(hostingController: UIHostingController<Content>, didZoom: Binding<Bool>) {
self.hostingController = hostingController
_didZoom = didZoom
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return hostingController.view
}
func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) {
didZoom = !(scrollView.zoomScale == scrollView.minimumZoomScale)
}
}
}
I finally found a workaround.
Add the following code inside the ZoomableScrollView-UIViewRepresentable class:
static func dismantleUIView(_ uiView: UIScrollView, coordinator: Coordinator) {
uiView.delegate = nil
coordinator.hostingController.view = nil
}

How to add Views on AVCaptureSession ?, like i have avcapturesession running but i cannot add a TextView below it

I am new to SwiftUI, and I was trying to building a app which recognizes objects using Resnet50 model and displays its recognized name, I was able to run the avcapturesession and print the objects name in console but i cant see the TextView when app is running, it always shows the camera fullScreen
This is my Content View
import SwiftUI
import AVKit
import Vision
struct ContentView: View {
#ObservedObject var cameraInfo = CameraViewController()
var body: some View {
VStack {
CameraView()
Text(cameraInfo.objectIdentifier)
}
.edgesIgnoringSafeArea(.top)
}
}
And this is my CameraView which is UIViewControllerRepresentable
struct CameraView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> CameraViewController {
return CameraViewController()
}
func updateUIViewController(_ uiViewController: CameraViewController, context: Context) {
print("Update Called")
}
typealias UIViewControllerType = CameraViewController
}
This is the CameraViewController
final class CameraViewController: UIViewController, ObservableObject,AVCaptureVideoDataOutputSampleBufferDelegate {
var captureSession: AVCaptureSession!
#Published var objectIdentifier: String = ""
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
guard let pixelBuffer: CVPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
guard let model = try? VNCoreMLModel(for: Resnet50().model) else { return }
let request = VNCoreMLRequest(model: model) { (finishedRequest, error) in
guard let results = finishedRequest.results as? [VNClassificationObservation] else { return }
guard let firstObservation = results.first else { return }
self.objectIdentifier = firstObservation.identifier
print("DEBUG: firstObs identifier\(firstObservation.identifier),confidence\(firstObservation.confidence)")
}
try? VNImageRequestHandler(cvPixelBuffer: pixelBuffer, options: [:]).perform([request])
}
override func viewDidLoad() {
super.viewDidLoad()
captureSession = AVCaptureSession()
captureSession.sessionPreset = .photo
guard let videoCaptureDevice = AVCaptureDevice.default(for: .video) else { return }
guard let input = try? AVCaptureDeviceInput(device: videoCaptureDevice) else {return}
captureSession.addInput(input)
captureSession.startRunning()
let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
view.layer.addSublayer(previewLayer)
previewLayer.frame = view.frame
let dataOutput = AVCaptureVideoDataOutput()
dataOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "video"))
captureSession.addOutput(dataOutput)
}
}

Want to convert this code into Pure SwiftUI code. no autolayout, no UIViewRepresentable, no UIHostingController

Code is working want to convert into Pure SwiftUI code. without UIViewRepresentable, UIHostingController please let me know how can I achieve. Thanks *
struct PathAnimatingView: UIViewRepresentable where Content: View {
var path: Path
let content: () -> Content
func makeUIView(context: UIViewRepresentableContext<PathAnimatingView>) -> UIView {
let view = UIView(frame: .zero)
let animation = CAKeyframeAnimation(keyPath: #keyPath(CALayer.position))
animation.duration = CFTimeInterval(10)
animation.repeatCount = 10
animation.path = path.cgPath
animation.keyTimes = [0.0,0.5,1.0]
animation.isRemovedOnCompletion = true
animation.fillMode = .forwards
animation.timingFunction = CAMediaTimingFunction(name: .linear)
let sub = UIHostingController(rootView: content())
sub.view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(sub.view)
sub.view.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
sub.view.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
view.layer.add(animation, forKey: "snake")
return view
}
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<PathAnimatingView>) {
print(uiView)
}
}

Changing background of NavigationView's bar when presented modally

I can't figure out a way to set the navigation bar to be opaque black...
All the related hacks don't seem to work if the navigation view is presented modally...
This is how I present my webView:
Button(action: { self.showFAQ.toggle() }) {
Text("Frequently Asked Questions").foregroundColor(.orange)
}.sheet(isPresented: $showFAQ) {
WebView(isPresented: self.$showFAQ, url: self.faqURL)
}
This is my webView wrapper:
struct WebView: View {
let url: URL
#Binding var isPresented: Bool
var body: some View {
NavigationView {
WebViewRepresentable(url: url)
.navigationBarTitle("", displayMode: .inline)
.navigationBarItems(trailing: Button(action: {
self.isPresented.toggle()
}, label: { Text("Done") } ))
}
}
init(isPresented: Binding<Bool>, url: URL) {
self.url = url
self._isPresented = isPresented
let appearance = UINavigationBarAppearance()
appearance.configureWithOpaqueBackground()
}
struct WebViewRepresentable: UIViewRepresentable {
let url: URL
// Creates a UIKit view to be presented.
func makeUIView(context: Context) -> WKWebView {
let webView = WKWebView()
webView.isOpaque = false
webView.backgroundColor = .systemBackground
return webView
}
// Updates the presented UIKit view (and its coordinator)
// to the latest configuration.
func updateUIView(_ uiView: WKWebView, context: Context) {
let req = URLRequest(url: url)
uiView.load(req)
}
}
}
UINavigationBarAppearance() is ignored... UINavigationBar.appearance() is also ignored...
A possible solution is to avoid using a NavigationView and simply add a Done button to achieve the same result:
struct WebView: View {
let url: URL
#Binding var isPresented: Bool
var body: some View {
VStack {
HStack {
Spacer()
Button(action: {
self.isPresented.toggle()
}) {
Text("Done").padding(.all, 20)
}
}
WebViewRepresentable(url: url)
}.background(Color.black.opacity(1.0))
.edgesIgnoringSafeArea(.all)
}
init(isPresented: Binding<Bool>, url: URL) {
self.url = url
self._isPresented = isPresented
let appearance = UINavigationBarAppearance()
appearance.configureWithOpaqueBackground()
}
struct WebViewRepresentable: UIViewRepresentable {
let url: URL
// Creates a UIKit view to be presented.
func makeUIView(context: Context) -> WKWebView {
let webView = WKWebView()
webView.isOpaque = false
webView.backgroundColor = .systemBackground
return webView
}
// Updates the presented UIKit view (and its coordinator)
// to the latest configuration.
func updateUIView(_ uiView: WKWebView, context: Context) {
let req = URLRequest(url: url)
uiView.load(req)
}
}
}