I am trying to set the top anchor point of a pdf that is inside of a view with the .ignoresSafeArea() modifier. I would also like it to work on the edges when the phone is in landscape although for simplicity I will only explain what I want for the top. I want it to function like the iOS Files app pdf viewer where when tapped it hides the navigation bars but the top of the pdf stays at the same place, but when you zoom in on the pdf it can fill the whole screen. When you zoom back out the top should return to the same place as before. Here is a simple view to show how it is being used:
@MainActor
struct ContentView: View {
@State var showBars: Bool = true
@State var pdfUrl: URL?
var body: some View {
NavigationStack {
GeometryReader { geo in
ScrollView {
TabView {
if let url = pdfUrl {
PDFViewer(pdfUrl: url)
.onTapGesture {
withAnimation {
showBars.toggle()
}
}
}
}
.tabViewStyle(.page(indexDisplayMode: .never))
.frame(width: geo.size.width, height: geo.size.height)
}
.scrollDisabled(true)
}
.ignoresSafeArea(edges: !showBars ? .all : [])
}
.task {
pdfUrl = renderPDF()
}
}
func renderPDF() -> URL {
let renderer = ImageRenderer(content: VStack {})
let url = URL.documentsDirectory.appending(path: "samplepdf.pdf")
renderer.render { size, context in
guard let pdf = CGContext(url as CFURL, mediaBox: nil, nil) else {
return
}
pdf.beginPDFPage(nil)
context(pdf)
pdf.endPDFPage()
pdf.beginPDFPage(nil)
context(pdf)
pdf.endPDFPage()
pdf.closePDF()
}
return url
}
}
And here is what my pdfView looks like so far:
struct PDFViewer: View {
var pdfUrl: URL
var body: some View {
PDFSheetView(document: .init(url: pdfUrl))
}
}
class PDFViewController: UIViewController {
let document: PDFDocument?
var pdfView: PDFView!
init(document: PDFDocument?) {
self.document = document
super.init(nibName: nil, bundle: nil)
}
override func loadView() {
let view = PDFView()
self.view = view
self.pdfView = view
view.document = document
view.displayDirection = .vertical
view.autoScales = true
view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
view.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
view.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
}
required init?(coder: NSCoder) {
document = nil
super.init(coder: coder)
return nil
}
override func viewDidLayoutSubviews() {
let bounds = view.bounds
if let document {
if let page = document.page(at: 0) {
let pageBounds = page.bounds(for: .mediaBox)
if bounds.width > 0 && pageBounds.width > 0 {
let scaleFactor = bounds.width / pageBounds.width
let subtractionFactor = scaleFactor * 0.0125
pdfView.minScaleFactor = scaleFactor - subtractionFactor
}
}
}
}
}
struct PDFSheetView: UIViewControllerRepresentable {
typealias UIViewControllerType = PDFViewController
let document: PDFDocument?
func makeUIViewController(context: Context) -> PDFViewController {
let controller = PDFViewController(document: document)
return controller
}
func updateUIViewController(_ uiViewController: PDFViewController, context: Context) {
}
}
Is this possible to do? Like I said before, I want it to function just like the iOS Files app pdf viewer.
You can achieve this by listening to the
PDFView
notification for scale changes. Then you can use aBinding<Bool>
to propagate the change to yourContentView
.This view seems kind of pointless (at least in the context of the question) but nevertheless pass the Binding on again.
Inside the ViewController add a Binding and subscribe to the NotificationCenter...