Detecting Page Changes in pdf using WebView in iOS Swift - ios

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)

Related

PDFKit: How to move current page in PDFView to a specific offset

Assuming there is only one page in the PDF.
What I am trying to achieve: Save Zoom and offset of a PDFPage currently being viewed and show the page at exact same offset and zoom level when user comes back to that page.
What I have achieved: Calculated the offset and zoom and on page reload, successfully shown saved zoom level of page. Iam unable to set the offset.
Tried using following methods, but no effect.
1)
[_pdfView goToRect:rect onPage:[_pdfView.document pageAtIndex: page.unsignedLongValue ]];
2)
PDFDestination *destination = [_pdfView.currentDestination initWithPage:[_pdfView.document pageAtIndex: page.unsignedLongValue ] atPoint:viewPoint];
[_pdfView goToDestination:destination];
I was able to accomplish this using goToRect. I think there are issues with goToDestination. It always navigates to a different location than I pass into it.
Here's what I do to save the location when exiting the pdf (please note that I translated this code from Swift and haven't tested in Objective C):
CGFloat savedScaleFactor = _pdfView.scaleFactor;
//I use this to find the first page shown in my view. If you only have one page,
//you can just use currentPage
PDFPage *page = [_pdfView pageForPoint:CGPointZero nearest:YES];
CGRect savedRect = [_pdfView convertRect:_pdfView.bounds toPage:page];
NSInteger savedIndex = [_pdfView.document indexForPage:page];
Then to restore the location:
_pdfView.scaleFactor = savedScaleFactor;
PDFPage *page = [_pdfView.document pageAtIndex:savedIndex];
[_pdfView goToRect:savedRect onPage:page];

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

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.

How to spawn random images from the middle of the screen to anywhere?

I am curious if it is possible to fire images from the middle of the screen, to anywhere on the screen with a random speed.
It is important that it will be done without files containing .sks, since Xcode is crashing when I try opening that kind of files. It has to be done in a UIViewController.
I already tried subclassing a view to a SKView but without success. Looking at existing projects always uses SKViews and files containing .sks which I can not open. I hope somebody can help me out!
Each of the images you need to "fire" should be an SKSpriteNode. They should be children of a SKScene. If you don't know how to create a SKScene or how to make the view controller present it, try creating a sample game project in Xcode - File > New Project > Game. It would create a nice sample project that would display some nice rectangles whenever you touch the screen.
Once you have your scene up and running, as well as your sprite nodes ready to fire, simply run these few lines for every node you need to fire from the middle of the screen:
let viewSize = UIScreen.main.bounds.size
let randomX = RandomInt(min: 0, max: viewSize.width)
let randomY = RandomInt(min: 0, max: viewSize.height)
let randomPosition = CGPoint(x: randomX, y: randomY)
let minDuration = 1 // Put here whatever value you feel fit for the minimum duration of flying
let maxDuration = 5 // Put here whatever value you feel fit for the maximum duration of flying
let randomDuration = TimeInterval(RandomInt(min: minDuration, max: maxDuration))
let fireAtWill = SKAction.move(to: randomPosition, duration: randomSpeed)
yourSpriteNode.runAction(fireAtWill)
Don't forget to replace "yourSpriteNode" with the name of the sprite node you need to fire.
Here is the RandomInt helper function:
func RandomInt(min: Int, max: Int) -> Int {
return Int(arc4random_uniform(UInt32(max-(min-1)))) + min
}
Your problem with sks files is an odd one - you may want to ask a separate question on that with more details.
You can use SpriteKit without sks files though - they are just a shortcut but everything can be done in code if you need/want to. See this SO answer for a discussion, an example and some useful links.

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

Resources