Why does PDFKit delete signature widgets that have already been digitally signed?
This should not happen.
Is there an undocumented flag that needs to be set so that PDFKit doesn't remove them when loading or saving the PDF?
It's difficult to tell if it is happening at
PDFDocument(url: fileURL)
or
document.write(to: outputURL)
If a document is signed and still allows annotations, form filling, comments, etc. we should be able to load the PDF into a PDFDocument and save it without losing the certs.
Instead the certs are gone and only the signature annotation widgets are present.
Here is a simple example of loading and then saving the PDF with out any changes and it shows that the data is actually being changed...
...
import UIKit
import PDFKit
class ViewController: UIViewController {
var pdfView: PDFView!
@IBOutlet weak var myButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
pdfView = PDFView(frame: self.view.bounds)
pdfView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.view.addSubview(pdfView)
self.view.bringSubviewToFront(myButton)
// Load and compare the PDF data
if let originalData = loadPDF() {
if let loadedData = getRawDataFromLoadedPDF() {
let isDataEqual = comparePDFData(originalData, loadedData)
print("Is original data equal to loaded data? \(isDataEqual)")
}
}
}
@IBAction func onSave(_ sender: Any) {
if let savedData = savePDF() {
if let originalData = loadPDF() {
let isDataEqual = comparePDFData(originalData, savedData)
print("Is original data equal to saved data? \(isDataEqual)")
}
}
}
func loadPDF() -> Data? {
guard let fileURL = Bundle.main.url(forResource: "document", withExtension: "pdf") else {
print("Error: document.pdf not found in bundle.")
return nil
}
do {
let originalData = try Data(contentsOf: fileURL)
if let document = PDFDocument(url: fileURL) {
pdfView.document = document
print("PDF loaded successfully.")
return originalData
} else {
print("Error: Unable to load PDF document.")
return nil
}
} catch {
print("Error reading PDF data: \(error)")
return nil
}
}
func getRawDataFromLoadedPDF() -> Data? {
guard let document = pdfView.document else {
print("Error: No document is currently loaded in pdfView.")
return nil
}
if let data = document.dataRepresentation() {
return data
} else {
print("Error: Unable to get raw data from loaded PDF document.")
return nil
}
}
func comparePDFData(_ data1: Data, _ data2: Data) -> Bool {
return data1 == data2
}
func savePDF() -> Data? {
guard let document = pdfView.document else {
print("Error: No document is currently loaded in pdfView.")
return nil
}
let fileManager = FileManager.default
let urls = fileManager.urls(for: .documentDirectory, in: .userDomainMask)
guard let documentsURL = urls.first else {
print("Error: Could not find the documents directory.")
return nil
}
let outputURL = documentsURL.appendingPathComponent("document_out.pdf")
if document.write(to: outputURL) {
print("PDF saved successfully to \(outputURL.path)")
do {
let savedData = try Data(contentsOf: outputURL)
return savedData
} catch {
print("Error reading saved PDF data: \(error)")
return nil
}
} else {
print("Error: Unable to save PDF document.")
return nil
}
}
}
Post
Replies
Boosts
Views
Activity
I'm creating an App that can accepted PDFs from a shared context.
I am using iOS, Swift, and UIKit with IOS 17.1+
The logic is:
get the context
see who is sending in (this is always unknown)
see if I can open in place (in case I want to save later)
send the URL off to open the (PDF) document and
load it into PDFKit's pdfView.document
I have no trouble loading PDF docs with the file picker.
And everything works as expected for shares from apps like Messages, email, etc... (in which case URLContexts.first.options.openInPlace == False)
The problem is with opening (sharing) a PDF that is sent from the Files App. (openInPlace == True)
If the PDF is in the App's Document Folder, I need the Security scoped resource, to access the URL from the File's App so that I can copy the PDF's data to the PDFViewer.document. I get Security scoped resource access granted each time I get the File App's context URL.
But, when I call fileCoordinator.coordinate and try to access a file outside of the App's document folder using the newUrl, I get an error.
FYI - The newUrl (byAccessor) and context url (readingItemAt) paths are always same for the Files App URL share context.
I can, however, copy the file to a new location in my apps directory and then open it from there and load in the data. But I really do not want to do that.
. . . . .
Questions:
Am I missing something in my pList or are there other parameters specific to sharing a file from the Files App?
I'd appreciate if someone shed some light on this?
. . . . .
Here are the parts of my code related to this with some print statements...
. . . . .
SceneDelegate
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
// nothing to see here, move along
guard let urlContext = URLContexts.first else {
print("No URLContext found")
return
}
// let's get the URL (it will be a PDF)
let url = urlContext.url
let openInPlace = urlContext.options.openInPlace
let bundleID = urlContext.options.sourceApplication
print("Triggered with URL: \(url)")
print("Can Open In Place?: \(openInPlace)")
print("For Bundle ID: \(bundleID ?? "None")")
// get my Root ViewController from window
if let rootViewController = self.window?.rootViewController {
// currently using just the view
if let targetViewController = rootViewController as? ViewController {
targetViewController.prepareToLoadSharedPDFDocument(at: url)
}
// I might use a UINavigationController in the future
else if let navigationController = rootViewController as? UINavigationController,
let targetViewController = navigationController.viewControllers.first as? ViewController {
targetViewController.prepareToLoadSharedPDFDocument(at: url)
}
}
}
. . . .
ViewController function
I broke out the if statement for accessingScope just to make it easier for me the debug and play around with the code in accessingScope == True
func loadPDF(fromUrl url: URL) {
// If using the File Picker / don't use this
// If going through a Share.... we pass the URL and have three outcomes (1, 2a, 2b)
// 1. Security scoped resource access NOT needed if from a Share Like Messages or EMail
// 2. Security scoped resource access granted/needed from 'Files' App
// a. success if in the App's doc directory
// b. fail if NOT in the App's doc directory
// Set the securty scope variable
var accessingScope = false
// Log the URLs for debugging
print("URL String: \(url.absoluteString)")
print("URL Path: \(url.path())")
// Check if the URL requires security scoped resource access
if url.startAccessingSecurityScopedResource() {
accessingScope = true
print("Security scoped resource access granted.")
} else {
print("Security scoped resource access denied or not needed.")
}
// Stop accessing the scope once everything is compeleted
defer {
if accessingScope {
url.stopAccessingSecurityScopedResource()
print("Security scoped resource access stopped.")
}
}
// Make sure the file is still there (it should be in this case)
guard FileManager.default.fileExists(atPath: url.path) else {
print("File does not exist at URL: \(url)")
return
}
// Let's see if we can open it in place
if accessingScope {
let fileCoordinator = NSFileCoordinator()
var error: NSError?
fileCoordinator.coordinate(readingItemAt: url, options: [], error: &error) { (newUrl) in
DispatchQueue.main.async {
print(url.path())
print(newUrl.path())
if let document = PDFDocument(url: newUrl) {
self.pdfView.document = document
self.documentFileName = newUrl.deletingPathExtension().lastPathComponent
self.fileLoadLocation = newUrl.path()
self.updateGUI(pdfLoaded: true)
self.setPDFScale(to: self.VM.pdfPageScale, asNewPDF: true)
} else {
print("Could not load PDF directly from url: \(newUrl)")
}
}
}
if let error = error {
PRINT("File coordination error: \(error)")
}
} else {
DispatchQueue.main.async {
if let document = PDFDocument(url: url) {
self.pdfView.document = document
self.documentFileName = url.deletingPathExtension().lastPathComponent
self.fileLoadLocation = url.path()
self.updateGUI(pdfLoaded: true)
self.setPDFScale(to: self.VM.pdfPageScale, asNewPDF: true)
} else {
PRINT("Could not load PDF from url: \(url)")
}
}
}
}
. . . .
Other relevant pList settings I've added are:
Supports opening documents in place - YES
Document types - PDFs (com.adobe.pdf)
UIDocumentBrowserRecentDocumentContentTypes - com.adobe.pdf
Application supports iTunes file sharing - YES
And iCloud is one for Entitlements with
iCloud Container Identifiers
Ubiquity Container Identifiers
. . . .
Thank you in advance!.
B
I published a new iPad app 30+ days ago with a few IAP items. Everything works correctly in testing. Now that it's a live app, everything is working as expected as well. You pay for a feature, the feature gets unlocked, and you get billed by Apple. This has been confirmed by myself and a few others who have made AIPs for the app. If the app is removed and added back, the restore purchases works as well.
But the problem is that the Trends/Proceeds show $0.00
Why am I not getting paid?
And why does App Connect not show any sales when there are confirmed sales since the first week it launched (over 30 days ago)?
All documents and banking info are up to date and I can see revenue from my other apps (they don't have AIP).
I have an iPad app that has 3 viewControllers. vcMain, vcMenu, vcInfo
vcMenu and vcInfo are custom sized windows
vcMain uses a segue to presentModally the vcMenu
The vcMenu has an info buttons that uses a segue to presentModally the vcInfo
After years, this no longer works as the vcMenu no longer appears to be the topmost window.
'Attempt to present vcInfo on vcMenu (from vcMenu whose view is not in the window hierarchy.'
I can bypass this issue when setting isModalInPresentation to true when vcMenu appears, but I do not want to change the behavior of the vcMenu by having to add a 'Close' button
Also setting isModalInPresentation to true in the info button function before calling performSegue does not work. It also does not work if I add a delay inside the function (ie perform a #selector).
Any suggestions?
Is there a new setting somewhere that I am missing?
Here is the code...
import UIKit
class vcMain: UIViewController {
// all functions perfomed in storyboard
// a button uses a segue to open vcMenu with
// Kind: presentModally
override func viewDidLoad() { super.viewDidLoad() }
}
class vcMenu: UIViewController {
let ID_POP_UP = "id1"
@IBAction func btnInfo(_ sender: UIButton) {
isModalInPresentation = true // get error
// performSegue(withIdentifier: ID_POP_UP, sender: nil)
perform(#selector(presentExampleController), with: nil, afterDelay: 2)
}
@objc private func presentExampleController() {
// get error even if add a 2s delay
performSegue(withIdentifier: ID_POP_UP, sender: nil)
}
@IBAction func btnBack(_ sender: UIButton) {
isModalInPresentation = false
self.dismiss(animated: true, completion: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let segueId = segue.identifier else { return }
if segueId == ID_POP_UP { print("ID_POP_UP: \(ID_POP_UP)") } // Just checking
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
//isModalInPresentation = true // no error
}
}
class vcInfo: UIViewController {
override func viewDidDisappear(_ animated: Bool) {
if let main = self.parent { main.isModalInPresentation = false } // testing
super.viewDidDisappear(animated)
}
@IBAction func btnCloseAll(_ sender: UIButton) {
self.view.window!.rootViewController?.dismiss(animated: true, completion: nil) // close all popovers
}
}
I have about 15 global Integer constants in my app. Half of them display zero.
For example...
struct Global {
struct lives {
static let free = 3
static let paid = 5
}
}
let CRATE_LOCATION_ZERO = CGPoint(x: -10000, y: -10000)
Then later in code I try to use them...
let purchasedApp = true
let newLives = purchasedApp ? paid : free
let newCrateLocation = CRATE_LOCATION_ZERO
When I run the program and get these values, they are always zero. When I add a break point and check the values, they are also zero.
The newLives will be 0
The CGPoint will be x:0, y:0
But not all the static (or constant) values end up being 0. Then some others have different values.
I've also put up a video to show you what I mean.
leckett.net/video/wierd.mov
CPU and memory are all okay.
I'm running Xcode 12.5.1
Has anybody experienced this?
I've added IAP to an iOS App that includes consumables and non-consumables. I'm going though each criteria for Testing at All Stages of Development with Xcode and Sandbox and Step 10 of Testing an Interrupted Purchase is not happening.
For the transaction...
I've set everything up
I get the transactionState .failed and
I call finishTransaction for the queue (as outlined in the docs)
After accepting the agreement, the payment queue does not receive a new transaction.
Is anyone experiencing the same thing? If yes, how to do get around updating the purchase?
If I call restoreCompletedTransactions on the queue, it will pull the transaction (FYI - I am doing a single purchase on a consumable item to start).
So does this mean I need to call up the queue on my own each time an interruption appears? (as there is no way to determine if and when the user completed the interrupted transaction).
I would appreciate input and how to handle this gracefully so that I can update the UI.
Thanks!
Hi - I'm implementing IAP and find the documentation a bit limited in error handling descriptions for the SKPaymentTransaction Failed Status and would like to know what others are doing.
For example, Apple has some of their own messages that appear during an IAP.
Also, when I user cancels somewhere along the way, some Apple messages appear depending on the context. (ie switch to a different store, not accepting T&Cs, change patent on the fly etc...). In other cases, they do not.
Displaying the error description to the user seems to be too much detail. Displaying a generic Transaction Failed seems too limited. Plus always displaying a message for a failed transaction may be redundant.
Does anyone have a recommend approach for what to display (or not display) for a failed transaction?
Greatly appreciate it. Thanks a lot...
Blaine
I'm using AVAudioPlayer to play background music in my app. When I invoke Siri, the player stops. After Siri answers the question background music no longer plays. How can I detect when Siri stops answering so that I can resume my background music?
I'd lke to hear peoples comments on what the best practices would be for the need to checking a condition before updating a User Interface element in iOS (including watchOS and tvOS).Here are some examples *1. An Undo button keeping track of changesfunc update(_ data: Data) {
updateHistory(data: Data)
if !btnUndo.isEnabled { btnUndo.isEnabled = true } // use this one or
btnUndo.isEnabled = true // use this one?
}2. Text in a label that hasn't changedvar timerMonitorHeartRate = Timer()
func startMonitoring() {
timerMonitorHeartRate.invalidate()
timerMonitorHeartRate = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] timer in
self?.displayHeartRate()
}
}
func displayHeartRate() {
let heartRateValue = BLEHeartRate.value // value comes from some async function
let heartRateString = String(format: "Heart Rate %1.2f", heartRateValue)
if (lblHearRate.text != heartRateString) { lblHearRate.text = heartRateString } // use this one or
lblHearRate.text = heartRateString // use this one?
}* these exmples are just off the top of my head, I didn't check them for syntax, only to give you an idea of what I'm talking about.Should we check the conditon before updating an item or does the underlaying code do that for us (i.e., the button will not actually undergo a paint refresh since it will internally check and already know that it is enabled). If this is the case, would the same thing happen when updating a text property with the same text?
I'm starting to work on BLE and would like some advice on best practices or what to look out for if any one has been down this route.For example, if 3 people are wearing the same Polar Bluetooth HRM (i.e., in a Gym) when the app starts up and scans for perifierials & services how can I choose which device to select?CBCentralManagerScanOptionAllowDuplicatesKey is off by default, to avoid battery drain.Will the closest HRM (strongest signal) appear in the CBPeripheral didDiscoverServiceslist first?Is there a way for the user to 'save' their device? - since there is no pairing option or specific UID available that I am aware of.Thanks...