PDFDestination is not giving me the right information (Apple PDFKit) - ios

I've got a PDF file of a technical drawing that contains one page with bookmarks linking to specific areas of the document.
When I click those bookmarks in Adobe Reader or Foxit Reader, it zooms to this specified area. But in Apple's Preview.app or iBooks or a PDFKit based iOS-App it won't work. Even PSPDFKit can't handle those bookmarks.
I'm building a PDFView based iOS-App that can handle those bookmarks. When I debug the PDFOutline property I can see that the contained PDFDestination object contains the information, but in private fields. I can watch them during debugging.
Also when I access the description property of the PDFDestination object, I'm getting the following:
FitR, page = 0, l = 132.0, b = 451.0, r = 400.0, t = 724.0
But the values zoom and point return 3.40282347e+38F which is the max value of CGFloat on 32-bit systems which means they're not properly set.
PDFView Config
Here's a litte more code, showing how I configured my PDFView
pdfView.document = document
pdfView.displayDirection = .horizontal
pdfView.autoScales = true
pdfView.usePageViewController(true, withViewOptions: nil)
pdfView.enableDataDetectors = false
pdfThumbnailView.thumbnailSize = CGSize(width: 44, height: 32)
pdfThumbnailView.pdfView = pdfView
pdfThumbnailView.layoutMode = .horizontal
Go to PDFOutline
// Load the first outline element
let outline = document.outlineRoot?.child(at: 0)?.child(at: 0)
let destination = outline!.destination
// This way to access the destination yields in the same thing
let destinationViaAction = (outline?.action as? PDFActionGoTo)?.destination
// Nothing happens here...
pdfView.go(to: destinationViaAction!)
// nor here
pdfView.go(to: destination!)
Questions
Is it a bug in PDFKit that the properties zoom and point of PDFDestination are not correctly calculated by those existing inset values?
Am I allowed to parse the description of PDFDestination and work with those values? Or will Apple refuse my App because of accessing private information?
Update
I filed a bug at Apple and will update this question when I get a response.

Related

iOS Charts combined chart only able to access some entries through chart functions

With the new 4.0 update in iOS Charts, I have redone all of my charts, including changing a lot of them to CombinedChartViews. however, now I am running into a very weird issue with accessing the entries using the in-built functions.
The data is presenting fine, as expected, but when highlighting, only some of the values are selecting. These are the same values that are only accessible through functions such as getEntryByTouchPoint.
guard let valOrig = self.getEntryByTouchPoint(point: location) else { return }
guard let val = lineBaseSet?.entries.getItemAt(Int(valOrig.x)) else { return }
print(val.x)
Only works for 40, 60, etc.
When I print lineData.first.entries, I get the correct count of 79 and all the entries are correct and in order. It's just when I am not accessing the array directly it can't seem to find most of the entries.
Entries working perfectly = 40, 60, 70, 75, 78, 79.
All others are not working.
This order of numbers seems to be exponential but I still don't see how this relates to the way it's not working.
I have only tested this on a CombinedChartView so not sure if it's working on other types.
Any help would be appreciated!!
EDIT: Sorry, bit of context, location is the location of a touch point, lineBaseSet is a custom variable equal to lineData?.first, and getItemAt() is the same as array[index] but it returns an optional as opposed to fatally crashing if out of index.
Ok, this isn't a direct answer to the question but I've figured out a workaround for other people having the same issue.
The functions 'valueForTouchPoint(point:axis:)' and 'getPosition(entry:axis:)' still work as expected so using the following, the entry and location of the entry can be found from the response of a UIPanGestureRecognizer:
let touchPoint = sender.location(ofTouch: 0, in: chartView)
let point = chartView.valueForTouchPoint(point: touchPoint, axis: .left)
selectedEntry = entries.first(where: { $0.x == round(Double(point.x)) })
chartView.getPosition(entry: selectedEntry, axis: .left)
From there, it's possible to create the horizontal and vertical lines using a CAShapeLayer and adding that as a sublayer to the chartView.
Hope this helps!

Detecting Page Changes in pdf using WebView in iOS Swift

I'm displaying a pdf stored in a Document directory in a WebView, i want to detect the changes of the pages within the pdf. I searched about it and got to know about the methods goBack() and goForward() but it didn't work for me, i think they are for switching between the files in webView but i want to detect the page switching within a current loaded pdf file. i didn't have a code for this to show as i'm still trying to figure it out. any sample code with an explanation will be really helpful. Thanks.
For this you have to scrollView to navigate particular position of page, like below.
let selectedPage: CGFloat = 2; // i.e. Go to page 5
let pageHeight: CGFloat = 1000.0; // i.e. Height of PDF page = 1000 px;
let y: CGFloat = pageHeight * selectedPage;
let pageOffset = CGPoint(x: 0, y: y)
self.webView.scrollView.setContentOffset(pageOffset, animated: true)

CGPDF drawPDFPage with rotation support

I'm saving a PDF file with CGPDF under iOS 10. For this I load an existing PDF page, and write it to a new file with a context. While doing so, the rotation information gets lost and the resulting PDF file has all pages re-arragend at 0°.
let writeContext: CGContext = CGContext(finalPDFURL, mediaBox: nil, nil)!
// Loop through all pages
let page: CGPDFPage = ...
var mediaBox = page.getBoxRect(.mediaBox)
writeContext.beginPage(mediaBox: &mediaBox)
writeContext.drawPDFPage(page)
writeContext.endPage()
// Loop finished
writeContext.closePDF()
Then I came up with this code, which handles rotation just fine, but seems to draw the content with a slight offset. Using it with a PDF which has text or anything else close to the margins, results in cut-off content. Tried later also setting x, y, etc. on the pageInfo dict but I guess I misunderstood something here, see 2nd question below.
let page: CGPDFPage = ...
// Set the rotation
var pageDict = [String: Int32]()
pageDict["Rotate"] = CGFloat.init(page.rotationAngle)
writeContext.beginPDFPage(pageDict as CFDictionary?)
writeContext.drawPDFPage(page)
writeContext.endPDFPage()
So my questions,
1) How to use the first approach but with rotation support? Or the second one, but without cropping of content?
2) Where would I find a complete listing of all available pageInfo key-value pairs for this method? https://developer.apple.com/reference/coregraphics/cgcontext/1456578-beginpdfpage
Thanks!
Question is old, but hope following answer will be useful for someone in the future. It will preserve the rotation information of the original PDF page
How to use the first approach but with rotation support?
let writeContext: CGContext = CGContext(finalPDFURL, mediaBox: nil, nil)!
// Loop through all pages
let page: CGPDFPage = ...
var mediaBox = page.getBoxRect(.mediaBox)
writeContext.beginPage(mediaBox: &mediaBox)
let m = page.getDrawingTransform(.mediaBox, rect: mediaBox, rotate: 0, preserveAspectRatio: true)
// Following 3 lines makes the rotations so that the page look in the right direction
writeContext.translateBy(x: 0.0, y: mediaBox.size.height)
writeContext.scaleBy(x: 1, y: -1)
writeContext.concatenate(m)
writeContext.drawPDFPage(page)
writeContext.endPage()
// Loop finished
writeContext.closePDF()
I got feedback from a fellow iOS coder who suggested following principle:
These three steps should allow you to recreate the original page in the destination, while maintaining the page rotation field: (1) set the source page rotation in the destination page via the page dictionary, (2) set that rotation (or possibly the rotation * -1?) in the CGContext you’re drawing into, and finally (3) explicitly set the media box in the destination to be identical to the source (no rotation).

Any way to automate SFSafariViewController in UI tests?

Is there any way to automate SFSafariViewController? I like the Xcode 7 UI test feature, but it seems it does not support SFSafariViewController automation. Some of the UI flows I am testing require a web browser so the app uses SFSafariViewController to make it safer vs a web view.
If it's similar to launching extensions (currently broken with direct interactions), try tapping the screen at the point where the element you're looking for is:
Example of tapping an action sheet which launches an extension:
func tapElementInActionSheetByPosition(element: XCUIElement!) {
let tableSize = app.tables.elementBoundByIndex(0).frame.size
let elementFrame = element.frame
// get the frame of the cancel button, because it has a real origin point
let CancelY = app.buttons["Cancel"].frame.origin.y
// 8 is the standard apple margin between views
let yCoordinate = CancelY - 8.0 - tableSize.height + elementFrame.midY
// tap the button at its screen position since tapping a button in the extension picker directly is currently broken
app.coordinateWithNormalizedOffset(CGVectorMake(elementFrame.midX / tableSize.width, yCoordinate / app.frame.size.height)).tap()
}
Note: You must tap at the XCUIApplication query layer. Tapping the element by position doesn't work.
For now Xcode 9.3 has support for that, but it doesn't work properly because of annoying Xcode bug.
In test you can print app.webViews.buttons.debugDescription or app.webViews.textFields.debugDescription, and it prints correct information, but after tap or typeText you have crash.
To workaround you can parse debugDescription, extract coordinates and tap by coordinate. For text field you can insert text via "Paste" menu.
private func coordinate(forWebViewElement element: XCUIElement) -> XCUICoordinate? {
// parse description to find its frame
let descr = element.firstMatch.debugDescription
guard let rangeOpen = descr.range(of: "{{", options: [.backwards]),
let rangeClose = descr.range(of: "}}", options: [.backwards]) else {
return nil
}
let frameStr = String(descr[rangeOpen.lowerBound..<rangeClose.upperBound])
let rect = CGRectFromString(frameStr)
// get the center of rect
let center = CGVector(dx: rect.midX, dy: rect.midY)
let coordinate = XCUIApplication().coordinate(withNormalizedOffset: .zero).withOffset(center)
return coordinate
}
func tap(onWebViewElement element: XCUIElement) {
// xcode has bug, so we cannot directly access webViews XCUIElements
// as workaround we can check debugDesciption, find frame and tap by coordinate
let coord = coordinate(forWebViewElement: element)
coord?.tap()
}
Article about that: https://medium.com/#pilot34/work-with-sfsafariviewcontroller-or-wkwebview-in-xcode-ui-tests-8b14fd281a1f
Full code is here: https://gist.github.com/pilot34/09d692f74d4052670f3bae77dd745889

Coordinate system doesn't seem to match between Playground and iOS Device/Simulator

I've been trying to wrap my head around all the goodness in Xcode 6 and iOS 8 over the last couple of days. I'm currently working with SceneKit to get a feel for what it can do.
I'm trying to build a visual grid to make placing objects in the scene a bit easier.
The Playground displays how I expect it to, but the Simulator/Device does not. I'm not sure if it's a bug, or if I'm doing something wrong.
I have the following code:
for index in -20..20 {
let i = CFloat(index)
let neg = i - 20
let pos = i + 20
var lat = [
SCNVector3Make(neg, 0, i),
SCNVector3Make(pos, 0, i)
]
var lng = [
SCNVector3Make(i, 0, neg),
SCNVector3Make(i, 0, pos)
]
var indices: CInt[] = [0, 1]
let latSource = SCNGeometrySource(vertices:&lat, count:2)
let lngSource = SCNGeometrySource(vertices:&lng, count:2)
let indexData = NSData(bytes:indices, length:sizeof(CInt) * countElements(indices))
let element = SCNGeometryElement(data:indexData, primitiveType:SCNGeometryPrimitiveType.Line, primitiveCount:2, bytesPerIndex:sizeof(CInt))
let latLine = SCNGeometry(sources:[latSource], elements:[element])
let lngLine = SCNGeometry(sources:[lngSource], elements:[element])
let latLineNode = SCNNode(geometry:latLine)
let lngLineNode = SCNNode(geometry:lngLine)
scene.rootNode.addChildNode(latLineNode)
scene.rootNode.addChildNode(lngLineNode)
}
In a Playground the second line is let i = CGFloat(index), but other than that the code is identical between the Playground and the iOS Xcode 6 project I have.
In the Playground, I get the grid I'm after. In the Simulator and on the Device, however, I get garbage. No matter how I change the SCNVector3Make calls I can't get the grid to display properly in iOS or the Simulator.
It should also be noted that what is displayed in the Simulator and on the device is identical.
I tried adding a box to the scene also. When I used an SCNBox it displays correctly - though much bigger than it should. When I use custom geometry (that works correctly in the Playground), however, the box dimensions are way off. It looks more like a wall than a cube.
I tried to include screenshots to show what I'm seeing, but apparently I need at least 10 reputation points to post images, sorry.
Thanks in advance!
UPDATE
To answer the commenter's question below (regarding how I initialize the scene):
In the project (that runs in the Simulator/Device) this is how I get it:
let scene = SCNScene()
let sceneView = SCNView(frame:UIScreen.mainScreen().bounds)
// Build up grid
sceneView.scene = scene
self.view = sceneView
In the Playground, I do this:
let sceneView = SCNView(frame: CGRect(x: 0, y: 0, width: 500, height: 300))
let scene = SCNScene()
sceneView.scene = scene
XCPShowView("The Scene View", sceneView)
// Build up grid
UPDATE 2
I created an OSX app, modified the generated GameViewController code to generate the same structures and everything worked as expected. I didn't update the box's color to be red, and the positioning of the cube and camera is a bit different in the OSX app.
Now that I have enough points, I will add images showing the what I'm seeing.
Also of note I tried this on both Xcode Beta 2 & 3 - the results were identical.
What I see in the Playground
What I see in the OSX app
What I see in the iOS Simulator
I removed iOS 8 from by phone and iPad, so i don't have any screen shots from those - but they look identical to the Simulator.
I'll be filing a bug report for this through Apple.
UPDATE 3
I have created repos for these projects so anyone who's interested can take a look (maybe there's something I'm not aware of that I'm doing wrong):
Playground project
OSX project
iOS project
Please let me know if you see anything I'm doing wrong or how I could be doing things better!
Thanks for your help everyone!
I had a similar issue recently playing with UIBezierPath in Playground. Upon posting the question in Apple developer forms, I believe Playground does use a different coordinate system (LLO) than the devices (ULO):
https://forums.developer.apple.com/message/39277#39277
Here is apple's docs on the two coordinate systems:
https://developer.apple.com/library/ios/documentation/2DDrawing/Conceptual/DrawingPrintingiOS/GraphicsDrawingOverview/GraphicsDrawingOverview.html#//apple_ref/doc/uid/TP40010156-CH14-SW10
you have does not use on first scene (frame:UIScreen.mainScreen().bounds).
In the project (that runs in the Simulator/Device) this is how I get it:
let scene = SCNScene()
let sceneView = SCNView(frame: CGRect(x: 0, y: 0, width: 500, height: 300))
// Build up grid
sceneView.scene = scene
self.view = sceneView
In the Playground, I do this:
let sceneView = SCNView(frame: CGRect(x: 0, y: 0, width: 500, height: 300))
let scene = SCNScene()
sceneView.scene = scene
XCPShowView("The Scene View", sceneView)

Resources