being that PDFKit is not available on iOS, how is it possible to get the outline of a pdf document in that environment? Is commercial libraries like FastPdfKit or PSPDFKit the only solution?
It's not TOO tricky to access the pdf outline. My outline parser has about 420 LOC. I'll post some snippets, so you'll get the idea. I can't post the full code as it's a commercial library.
You basically start like this:
CGPDFDictionaryRef outlineRef;
if(CGPDFDictionaryGetDictionary(pdfDocDictionary, "Outlines", &outlineRef)) {
going down to
NSArray *outlineElements = nil;
CGPDFDictionaryRef firstEntry;
if (CGPDFDictionaryGetDictionary(outlineRef, "First", &firstEntry)) {
NSMutableArray *pageCache = [NSMutableArray arrayWithCapacity:CGPDFDocumentGetNumberOfPages(documentRef)];
outlineElements = [self parseOutlineElements:firstEntry level:0 error:&error documentRef:documentRef cache:pageCache];
}else {
PSPDFLogWarning(#"Error while parsing outline. First entry not found!");
}
you parse single items like this:
// parse title
NSString *outlineTitle = stringFromCGPDFDictionary(outlineElementRef, #"Title");
PSPDFLogVerbose(#"outline title: %#", outlineTitle);
if (!outlineTitle) {
if (error_) {
*error_ = [NSError errorWithDomain:kPSPDFOutlineParserErrorDomain code:1 userInfo:nil];
}
return nil;
}
NSString *namedDestination = nil;
CGPDFObjectRef destinationRef;
if (CGPDFDictionaryGetObject(outlineElementRef, "Dest", &destinationRef)) {
CGPDFObjectType destinationType = CGPDFObjectGetType(destinationRef);
The most annoying thing is that you have Named Destinations in most pdf documents, which need additional steps to resolve. I save those in an array and resolve them later.
It took quite a while to "get it right" as there are LOTS of differences in the PDFs that are around, and even if you implement everything in compliance to the PDF reference, some files won't work until you apply further tweaking. (PDF is a mess!)
It is now possible in iOS 11+.
https://developer.apple.com/documentation/pdfkit
You can get the PDFOutline of a PDFDocument.
The PDFOutline's outlineRoot will return outline items if there are any and NULL if none.
Related
I am working on an ancient iOS app for a program that runs off VB6. It is passing strings over in a non unicode format including Hebrew characters which once they are parsed are displayed as "àáðø ãøåøé"
I am assuming it is being encoded in Windows Hebrew.
I can't seem to find anything in the apple documentation that explains how to handle this case. And most searches bring up solutions in Swift, no Obj-C. I tried this:
NSString *hebrewPickup = [pickupText stringByApplyingTransform:NSStringTransformLatinToHebrew reverse:false];
But that just gave me this:
"ðø ַ̃øַ̊øֵ"
I am stumped.
EDIT: Based on JosefZ's comment I have tried to encode back using CP1252, but the issue is that CP1255 is not in the list of NSStringEncodings. But seems like it would solve my issue.
NSData *pickupdata = [pickupText dataUsingEncoding:NSWindowsCP1252StringEncoding];
NSString *convPick = [[NSString alloc] initWithData:pickupdata encoding:NSWindowsCP1254StringEncoding];
NSString *hebrewPickup = [convPick stringByApplyingTransform:NSStringTransformLatinToHebrew reverse:false];
Ok, if any poor soul ends up here, this is how I ended up fixing it. I needed to add some Swift into my Obj-C code. (If only I could magically just rebuild the whole project in Swift instead.)
Here is the info on that: https://developer.apple.com/documentation/swift/imported_c_and_objective-c_apis/importing_swift_into_objective-c
Making use of this Swift Package: https://github.com/Cosmo/ISO8859
I added the following code to a new swift file.
#objc class ConvertString: NSObject {
#objc func convertToHebrew(str:String) -> NSString {
let strData = str.data(using: .windowsCP1252);
let bytes: Data = strData!;
if let test = String(bytes, iso8859Encoding: ISO8859.part8) {
return test as NSString;
}
let test = "";
return test as NSString;
}
}
Then in the Obj-C project I was able to call it like so:
ConvertString *stringConverter = [ConvertString new];
NSString *pickupTextFixed = [stringConverter convertToHebrewWithStr:pickupText];
NSString *deliverTextFixed = [stringConverter convertToHebrewWithStr:deliverText];
This is probably simple enough, but Im not a regular iOS dev but have a decent understanding of Obj-C and Xcode however not so much with Swift yet.
I am playing around with a mapping SDK called Skobbler and downloaded both Swift and iOS examples.
The Swift example gives me some useful text/console logs of directions turn by turn however the Obj-C doesn't and having a tough time exposing them.
The Swift example looks like this
let advices: Array<SKRouteAdvice> = SKRoutingService.sharedInstance().routeAdviceListWithDistanceFormat(SKDistanceFormat.Metric) as! Array<SKRouteAdvice>
for advice: SKRouteAdvice in advices
{
let instructions = advice.adviceInstruction
print(instructions)
}
NSArray<SKRouteAdvice *> *advices = [[SKRoutingService sharedInstance] routeAdviceListWIthDistanceFormat:SKDistanceFormat.Metric];
for (SKRouteAdvice *advice in advices)
{
NSLog(#"%#", instructions);
}
NSArray *advices = [[SKRoutingService sharedInstance]routeAdviceListWithDistanceFormat:SKDistanceFormat.Metric];
for (SKRouteAdvice *advice in advices) {
NSLog(#"%#", [advice adviceInstruction]);
}
as! doesn't need translating - the compiler will just believe you (you might want to add an assert for identical behaviour). The print is replaced with NSLog (#"%#", instructions).
I want to make a multi level offline search in my app.
I followed the directions at official Skobbler page and only difference is that l did not download map of France, but map of Wyoming instead.
Offline package code for it is USWY if I am right.
-(void)prepareForSearch{
[SKSearchService sharedInstance].searchServiceDelegate = self;
[SKSearchService sharedInstance].searchResultsNumber = 500;
_listLevel = SKCountryList;
_searchSettings = [SKMultiStepSearchSettings multiStepSearchSettings];
_searchSettings.listLevel = _listLevel;
_searchSettings.offlinePackageCode = #"USWY";
_searchSettings.parentIndex=-1;
}
- (IBAction)searchAction:(UIButton *)sender {
_searchSettings.searchTerm = [NSString stringWithFormat:#"%#",_searchBar.text];
[[SKSearchService sharedInstance]startMultiStepSearchWithSettings:_searchSettings];
}
-(void)searchService:(SKSearchService *)searchService didRetrieveMultiStepSearchResults:(NSArray *)searchResults
{
if ([searchResults count] !=0 && _listLevel<SKInvalidListLevel){
if (_listLevel == SKCountryList) {
_listLevel = SKCityList;
}
else{
_listLevel++;
}
SKSearchResult *searchResult = searchResults[0];
SKMultiStepSearchSettings* multiStepSearchObject = [SKMultiStepSearchSettings multiStepSearchSettings];
multiStepSearchObject.listLevel = _listLevel++;
multiStepSearchObject.offlinePackageCode = _searchSettings.offlinePackageCode;
multiStepSearchObject.searchTerm = _searchBar.text;
multiStepSearchObject.parentIndex = searchResult.identifier;
[[SKSearchService sharedInstance]startMultiStepSearchWithSettings:multiStepSearchObject];
}
}
-(void)searchServiceDidFailToRetrieveMultiStepSearchResults:(SKSearchService *)searchService
{
NSLog(#"Multi Level Search failed");
}
Whatever I put as a searchTerm, I end up with "MultiLevel Search Failed".
from this screenshot, you can see that my map package for Wyoming is included in my SKMaps.bundle:
(Also, if anyone can answer me this: Versioning was different in my app and in the simulator folder in the test app, from where I downloaded an offline package. So, for testing purposes, I made two folders and put Wyoming package in both of them(20140807 and 20140910). Are there any rules regarding this?)
What could be the problem?
Ok, after few days I managed to find the source of the problem.
First, I found out which version I'm using and it's the 20140910.
Second, For some reason, the entire folder containing maps was not recognised. So I took the entire SKMaps.bundle, together with some pre-bundled maps from the demo app, provided by the Skobbler team, and put it in my project and now everything works fine.
I'm having a problem when using the UIActivityViewController to share spreadsheets and videos (and probably other file types). The problem is that the right type identifiers are not found. For example, when I try to attach a spreadsheet, as seen in the code below:
let testFilePath = NSBundle.mainBundle().pathForResource("test_spreadsheet", ofType: "xlsx")
let testData = NSData(contentsOfFile: testFilePath)
let list = [testData]
let activityViewController = UIActivityViewController(activityItems: list, applicationActivities: nil)
navigationController.presentViewController(activityViewController, animated: true, completion: nil)
I get the following log message:
2014-08-20 16:45:58.035 TestExtensionPoints[12608:194174] Discovered extensions: {(
<NSExtension: 0x7fb7fca13680> {id = com.asperasoft.faspex-app.FaspexExtension}
)} for attributes: {
NSExtensionActivationRule = {
extensionItems = (
{
attachments = (
{
registeredTypeIdentifiers = (
"public.data"
);
}
);
}
);
};
NSExtensionPointName = (
"com.apple.share-services",
"com.apple.ui-services",
"com.apple.services"
);
}
Note that the registered type identifier is public.data, instead of public.spreadsheet.
This differs from when I try to attach, say, a pdf or a png file. If I just change the file to a pdf, as seen in the code below (only the first line changes):
let testFilePath = NSBundle.mainBundle().pathForResource("test_pdf", ofType: "pdf")
I get the following log message, which indeed DOES have the desired type identifier:
2014-08-20 16:50:52.973 TestExtensionPoints[12652:196132] Discovered extensions: {(
<NSExtension: 0x7ff2534285a0> {id = com.asperasoft.faspex-app.FaspexExtension}
)} for attributes: {
NSExtensionActivationRule = {
extensionItems = (
{
attachments = (
{
registeredTypeIdentifiers = (
"com.adobe.pdf"
);
}
);
}
);
};
NSExtensionPointName = (
"com.apple.share-services",
"com.apple.ui-services",
"com.apple.services"
);
}
Does anyone know why this is happening, and does anyone have a workaround? I know I can just write the files and attach URLs, but that's not really the purpose of this exercise.
Extra Background: I'm mainly asking this because I'm making a share extension, and am experimenting with how the data will be passed to me. I have already covered the case where it is passed as a URL and as a UIImage. These other cases would be fine, except for the fact that I am not getting the type identifiers I need.
The system probably sniffs for familiar types like PNG and PDF and not for esoteric types. Since PNG and PDF can be identified from the starting bytes, the activity system doesn't strictly need a UTI to identify them. However, the same may not be true of esoteric types like spreadsheets.
What you need is to implement UIActivityItemSource or subclass UIActivityItemProvider and provide these instances to the UIActivityViewController instead of naked NSData. This allows you to provide the UTI of the data explicitly to the UIActivityViewController.
For immediately available data, implement these UIActivityItemSource methods:
- activityViewControllerPlaceholderItem: to return the minimum data item that conforms to the type. You can probably pass an empty NSData here.
- activityViewController:itemForActivityType to return the actual data item.
- activityViewController:dataTypeIdentifierForActivityType: to return the UTI of the data item.
For data that can be lazily generated in another thread, implement these UIActivityItemProvider methods:
- item to return the actual data item. This will be called in a different thread, so your generation has to be thread safe.
- activityViewController:dataTypeIdentifierForActivityType: to return the UTI of the data item.
Now if the UTI is not a common UTI you'll also have to export them from your Info.plist.
Perhaps there's not a UTI for .xlsx files?
This question seemed to have a similar issue. The answer provided answers for .xlsx files, and more UTIs are listed here with instructions on implementation here.
All:
I am recording a movie, using AVCaptureMovieFileOutput. As various events occur, I wish to store the event's title/time in the QuickTime movie being written. Thus I might have 20-30 data points that I wish to associate with a particular movie.
My strategy is to use metadata, but I have not been having much luck. Can someone please tell me, first of all:
a) Can I store arbitrary metadata, or just those keys and values as defined in AVMetadataFormat.h? I would like to be able to store an array.
b) If I can store an arbitrary array, what key does the trick? If not, could I store my metadata in a comment field (ugly, but I could parse 20-30 points quickly enough).
c) The code shown below does not appear to work, as no matter what I put in for the item.key (AVMetadataQuickTimeMetadataKeyArtist, AVMetadataCommonKeyArtist, or all sorts of other things ending in Artist) I never see anything in iTune's Get Info window.
- (IBAction)recordEvent:(id)sender {
NSLog(#"Record a metadata point here ...");
// is there any metadata associated with the file yet?
NSArray * existingMetaData = self.aMovieFileOutput.metadata;
NSMutableArray * newMetadataArray = nil;
if(existingMetaData){
newMetadataArray = [existingMetaData mutableCopy];
} else {
newMetadataArray = [[NSMutableArray alloc]init];
}
AVMutableMetadataItem * item = [[AVMutableMetadataItem alloc]init];
item.keySpace = AVMetadataKeySpaceCommon;
item.key = AVMetadataQuickTimeMetadataKeyArtist;
item.value = #"Enya, really!"; // in practice this will be the title of (UIButton *)sender
item.time = CMTimeMake(0.0,1.0);
[newMetadataArray addObject:item];
self.aMovieFileOutput.metadata = newMetadataArray;
}
Any advice would be greatly appreciated.
Thanks!
Storing metadata in QuickTime file via AVCaptureMovieFileOutput & AVMutableDataItems only allows you to store values for keys predefined in AVMetadataKeySpaceCommon keyspace, i. e. AVMetadataCommonKey* like keys
All the other data is ignoring