How to get the current page number of a PDF? - ios

There is a default Core Graphics method that tells the current active PDF page number.
Is there any way to get the current active page number as showing by the page indicator?
And also is there any way to hide the the page indicator?
Answer in Objective-C would be appreciated.

It is easy to get page number in PDFKit. Please find the below code snippet.
print(pdfDocument!.index(for: pdfView.currentPage!))

Swift 5.1
A Simple Solution
#IBOutlet var pageNumber: NSTextField!
let curPg = self.thePDFView.currentPage?.pageRef?.pageNumber
pageNumber.stringValue = "Page \(String(describing: curPg!))"

This is an old question and the answer should be in Objc but I had to do a quite similar approach in Swift and here's my solution.
First I created a outlet for container, a label for current page and the another label for current page;
#IBOutlet weak var pageInfoContainer: UIView!
#IBOutlet weak var currentPageLabel: UILabel!
#IBOutlet weak var totalPagesLabel: UILabel!
Also a timer that will explain below;
private var timer: Timer?
To get the current page a soon as it changes you need to add an observer;
NotificationCenter.default.addObserver(self, selector: #selector(handlePageChange), name: Notification.Name.PDFViewPageChanged, object: nil)
Do not forget to remove the observer in viewWillDisappear!
Now that you have everything in place let's apply some logic!
Here's my setup for pdfView;
private func setupPdfView() {
if let pdf: PDFDocument = pdfDocument {
pdfView.autoScales = true
pdfView.backgroundColor = .clear
pdfView.document = pdf
pdfView.usePageViewController(true, withViewOptions: nil)
}
if let totalPages: Int = pdfView.document?.pageCount {
totalPagesLabel.text = String(totalPages)
}
}
The method that will handle the pageChange through the observer;
#objc func handlePageChange() {
if let currentPage: PDFPage = pdfView.currentPage, let pageIndex: Int = pdfView.document?.index(for: currentPage) {
UIView.animate(withDuration: 0.5, animations: {
self.pageInfoContainer.alpha = 1
}) { (finished) in
if finished {
self.startTimer()
}
}
currentPageLabel.text = String(pageIndex + 1)
}
}
And finally the timer and method;
private func startTimer() {
timer?.invalidate()
timer = Timer.scheduledTimer(timeInterval: 2.0, target: self, selector: #selector(whenTimerEnds), userInfo: nil, repeats: false)
}
#objc func whenTimerEnds() {
UIView.animate(withDuration: 1) {
self.pageInfoContainer.alpha = 0
}
}
Bear in mind that the timer is being used as a "candy" and is completely optional, this will remove the counter after X seconds on the same page
Stay safe!

Objective C
CGPDFDocumentRef pdf = CGPDFDocumentCreateWithURL((CFURLRef)[NSURL fileURLWithPath:#"pdf path"]);
int pageCount = CGPDFDocumentGetNumberOfPages(pdf);
CGPDFDocumentRelease(pdf);
Set delegate
self.webView.delegate = self;
Implement webViewDidFinishLoad method and you will get page count as follow...
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSInteger pageCount = webView.pageCount;
//OR
NSInteger count_horizontal = webView.scrollView.contentSize.width / webView.scrollView.frame.size.width;
NSInteger count_verticel = webView.scrollView.contentSize.height / webView.scrollView.frame.size.height;
}
Swift version for other
Get number of pages from pdf file.
guard var pdf = CGPDFDocument(URL(fileURLWithPath: "pdf path") as CFURL) else {
print("Not able to load pdf file.")
return
}
let pageCount = CGPDFDocumentGetNumberOfPages(pdf);
Display pdf in web view and set its delegate
self.webView.delegate = self;
Implement webViewDidFinishLoad method and you will get page count as follow...
extension YourViewController: UIWebViewDelegate {
func webViewDidFinishLoad(_ webView: UIWebView) {
let count = webView.pageCount
// OR
// when horizontal paging is required
let count_horizontal = webView.scrollView.contentSize.width / webView.scrollView.frame.size.width;
// when vertical paging is required
let count_vertical = webView.scrollView.contentSize.height / webView.scrollView.frame.size.height;
}
}

Related

How to listen for change in variable inside a function

I have a class called Observers to observe Firebase Storage Upload Tasks, but before observing the progress, it waits for PHPickerviewcontroller to upload the video. I have an instance variable in my class, hasUploaded so that I can know when I can start to change the progress bar, however, with the way it's set up, the block of code in the if statement will never be called. I know there is didSet but that doesn't help me in this case, because I need to listen for change inside the function. How do I do that?
func observeProgress(progressWheel: UIActivityIndicatorView, errorLabel: UILabel, progressView: UIProgressView, progressLabel: UILabel)
{
progressLabel.text = "Downloading from Device...Please Wait (1/2)"
progressWheel.startAnimating()
progressWheel.alpha = 1
inProgress = true
//RIGHT HERE - Wait for hasUploaded to == true
if hasUploaded
{
progressWheel.alpha = 0
self.taskReference!.observe(.progress)
{ (snapshot) in
guard let progress = snapshot.progress?.fractionCompleted else { /**alert**/ return }
progressView.progress = Float(progress)
progressLabel.text = "\(round(100 * Float(progress)))% (2/2)"
if progress == 1
{
progressLabel.text = "Upload Successful!"
progressLabel.textColor = .black
progressView.progress = 0
}
}
}
}
I thought about it again and maybe it is easier for you to use the NotificationCenter.
In the Model or ViewController add
let nc = NotificationCenter.default
and thenadjust the hasUploaded variable to
var hasUploaded = false {
didSet {
let statusChange = ["userInfo": ["hasUploaded": hasUploaded]]
NotificationCenter.default
.post(name:
NSNotification.Name("com.user.hasUploaded"),
object: nil,
userInfo: statusChange)
}
}
In the controller with the function observeProgress, also add
let nc = NotificationCenter.default
Add the following to the viewDidLoad() function
NotificationCenter.default
.addObserver(self, selector:#selector(hasUploadedNotificationReceived(_:)),
name: NSNotification.Name ("com.user.hasUploaded"),
object: nil)
}
Finally create the function hasUploadedNotificationReceived() (which is called above whenever the notification will be received) to add the magic that should happen after the the change. For example:
#objc func hasUploadedNotificationReceived(_ notification: Notification){
let notification = notification.userInfo?["userInfo"] as? [String: Bool] ?? [:]
if (notification["hasUploaded"] as? Bool)! {
observeProgress(...) {
[...]
}
}
}
Please read also the documentation to figure out what options you have and what you can add or modify.
Beside this implementation, I also can imagine that the a Delegate as well as Combine and as #matt mentioned async/await could help to achieve your desired behavior.

AVPlayerViewController show black screen some times

I create an player and working fine in most part of times.
In some situations (that I didn't realized why) screen video stays black with a play button that does nothing.
I verified url and is ok, that's not the problem.
In my viewController I can call this block of code multiples times with different urls, that's why I 'restart' AVPlayerViewController.
// Create an var in class...
// ....
self.videoPlayerViewController?.player?.pause()
self.videoPlayerViewController = AVPlayerViewController()
self.videoPlayerViewController?.player = viewModel.avPlayer
if let avController = self.videoPlayerViewController {
self.add(avController, in: self.playerView)
avController.player?.play()
} else {
// Error
}
That's function add:
extension UIViewController {
func add(_ viewController: UIViewController, in view: UIView) {
viewController.view.frame = view.bounds
addChildViewController(viewController)
view.addSubview(viewController.view)
viewController.didMove(toParentViewController: self)
view.clipsToBounds = true
}
}
Someone knows what is wrong?
Thanks in advance!!
After so many time.. I found solution.
The problem was that I wasn't cleaning AVPlayer inside AVPlayerController. And I also added New instance inside a DispachQueue.
That's new code:
self.videoPlayerViewController?.player?.pause()
self.videoPlayerViewController?.player = nil
self.videoPlayerViewController = nil
self.videoPlayerViewController = AVPlayerViewController()
self.videoPlayerViewController?.player = viewModel.avPlayer
And after I added in viewController:
if let avController = self.videoPlayerViewController {
DispatchQueue.main.async { [weak self] in
if let strongSelf = self {
strongSelf.add(avController, in: strongSelf.playerView)
avController.player?.play()
}
}
} else {
// Error
}
I hope it could help someone!!

How to make slider value change object or function?

I am new to the site and Swift so any feedback on question technique is gratefully received.
I am trying to make the value of a slider change both the audio that it being played and the text label (or image).
I have created my outlets:
#IBOutlet weak var audioSlider: UISlider!
#IBOutlet weak var audioValue: UILabel!
And my action:
#IBAction func audioSliderValueChanged(sender: UISlider) {
var currentValue = (sender.value)
audioValue.text = (StringInterpolationConvertible: audioValue))
All connected to one slider. I am unsure of how to approach this issue - would an if else work?
EDIT: Han Yong Code:
There was already a 'do' for other sounds within the app using AVFoundation. I imported Foundation (AVFoundation was already imported)
I have added:
#IBOutlet weak var audioSlider: UISlider!
#IBOutlet weak var audioValue: UILabel!
var audioSliderPlayer:AVAudioPlayer = AVAudioPlayer()
var currentTime: Double {
get {
return CMTimeGetSeconds(audioSliderPlayer.currentTime)
}
set {
let newTime = CMTimeMakeWithSeconds(newValue, 1)
audioSliderPlayer.seekToTime(newTime, toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero)
}
}
And added the following after the do function (within viewDidLoad):
{timeObserverToken = audioSliderPlayer.addPeriodicTimeObserverForInterval(interval, queue: dispatch_get_main_queue()) {
[weak self] time in
self?.timeSlider.value = Float(CMTimeGetSeconds(time))
}}
}
I have updated the action:
#IBAction func timeSliderDidChange(sender: UISlider) {
currentTime = Double(sender.value)
}
However, I am still having issues with errors. Also, how would I be able to specify which audio it plays dependent on value?
I guess you want the audioValue.text show the same value of slider, right?
Try this:
#IBAction func audioSliderValueChanged(sender: UISlider) {
var currentValue = (sender.value)
audioValue.text = "\(currentValue)"
}
If you use AVFoundation and AVPlayer, sample source that apple provide will be helpful.
In this source, assign var that has get and set method. In set method, update your label and player state. (Sample source snippet don't update label, but you can make it.)
var currentTime: Double {
get {
return CMTimeGetSeconds(player.currentTime())
}
set {
let newTime = CMTimeMakeWithSeconds(newValue, 1)
player.seekToTime(newTime, toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero)
}
}
If slider changed, it trigger variable's set method. Label and player status will be updated.
#IBAction func timeSliderDidChange(sender: UISlider) {
currentTime = Double(sender.value)
}
Add addPeriodicTimeObserverForInterval method to AVPlayer. It will be called every interval you set and trigger your variable's setter.
timeObserverToken = player.addPeriodicTimeObserverForInterval(interval, queue: dispatch_get_main_queue()) {
[weak self] time in
self?.timeSlider.value = Float(CMTimeGetSeconds(time))
}

Cannot load/scroll full pdf in swift WebView.

I am trying to load a pdf using web view using swift. It can load only one page of the pdf, cannot scroll down more than one page. What can i do?
import UIKit
class ViewController: UIViewController,UIWebViewDelegate {
#IBOutlet var webViews: UIWebView!
var path = ""
override func viewDidLoad() {
super.viewDidLoad()
path = NSBundle.mainBundle().pathForResource("ibook", ofType: "pdf")!
let url = NSURL.fileURLWithPath(path)
/*webViews.scalesPageToFit = true
webViews.scrollView.scrollEnabled = true
webViews.userInteractionEnabled = true*/
webViews.delegate = self
self.webViews.loadRequest(NSURLRequest(URL: url!
))
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func webViewDidStartLoad(webView : UIWebView) {
//UIApplication.sharedApplication().networkActivityIndicatorVisible = true
println("webViewDidStartLoad")
}
func webViewDidFinishLoad(webView : UIWebView) {
//UIApplication.sharedApplication().networkActivityIndicatorVisible = [enter image description here][1]false
webViews.scalesPageToFit = true
webViews.scrollView.scrollEnabled = true
webViews.userInteractionEnabled = true
println("webViewDidFinishLoad")
}
}
I've bumped into the similar problem while trying to display external pdf (not the bundled one), but I suppose you can use the same fix.
In your webViewDidFinishLoad, check if the url is actually a pdf one. Because in my case I know what I'm expecting, I used simple dumb checking. If url links to a pdf, you need to reload the web view to show it correctly and hence be able to scroll.
Here is a bit simplified code in objective C. It should be quite similar in Swift. Try something like this:
- (void)webViewDidFinishLoad:(UIWebView *)webView {
static BOOL isPdfReloaded = NO;
if (!isPdfReloaded && [webView.request.URL.absoluteString containsString:#".pdf"])
{
[webView reload];
isPdfReloaded = YES;
}
else
{
isPdfReloaded = NO;
}
}
The Best solution is to migrate from UIWebView to WkWebView

Making a non-instantaneous change with an #IBAction button in Swift

I am trying to change the value of the speech rate property of my AVSpeechUtterance upon clicking a button in my app. I then want this value to carry over when I press the "speak" button.
If that explanation wasn't clear enough imagine this:
There are three buttons: one, two and three.
When the user presses one, the value of a the rate of an utterance changes (The same goes for the two and three).
Then after pushing one of the first three buttons, the user pushes another button that activates an utterance. This utterance will carry over the rate value and apply it to the speech.
Here is the code I have tried but doesn't work:
import UIKit
import AVFoundation
class SecondViewController: UIViewController {
#IBOutlet var minutesLabel: UITextField!
#IBOutlet var secondsField: UITextField!
func speak(sender: AnyObject) {
let speechUtterance = AVSpeechUtterance(string:exampleSpeech)
speechSynthesizer.speakUtterance(speechUtterance)
}
#IBOutlet var result: UILabel!
#IBAction func verySlow(sender: UIButton) {
let verySlowButtonPressed = true
let talkingSpeed = 90
let minutesValue : Double = (minutesLabel.text as NSString!).doubleValue
let secondsValue = (secondsField.text as NSString!).doubleValue
let secondsToMinutes = secondsValue / 60
let compiledTime = Double(minutesValue) + Double(secondsToMinutes)
let resultingWords = Double(compiledTime) * Double(talkingSpeed)
let resultCeiling = ceil(resultingWords)
result.text = "\(String(format: "%.0f", resultCeiling)) words"
if verySlowButtonPressed {
speechUtterance.rate = 0.25
speechUtterance.pitchMultiplier = 0.25
speechUtterance.volume = 0.75
}
}
#IBAction func speakButton(sender: AnyObject) {
speak(exampleSpeech)
}
In your speak function you are creating a new AVSpeechUtterance instance every time it's called. When your verySlow function is called, it's setting the rate, pitchMultiplier, and volume on what I'm presuming is a class property called speechUtterance. Since you're creating a new AVSpeechUtterance in speak each time it's called, those properties that you are setting in verySlow are not going to be set on the same instance.
One way to solve this would be to make rate, pitchMultiplier, and volume properties in your class, set those in your verySlow function, and then set them on speechUtterance after you create it in your speak function.
Something like:
var rate: Float?
var pitchMultiplier: Float?
var volume: Float?
func speak(sender: AnyObject) {
let speechUtterance = AVSpeechUtterance(string:exampleSpeech)
if let rate = self.rate {
speechUtterance.rate = rate
}
if let pitchMultiplier = self.pitchMultiplier {
speechUtterance.pitchMultiplier = pitchMultiplier
}
if let volume = self.volume {
speechUtterance.volume = volume
}
speechSynthesizer.speakUtterance(speechUtterance)
}
#IBAction func verySlow(sender: UIButton) {
// ... whatever other code you need here ...
self.rate = 0.25
self.pitchMultiplier = 0.25
self.volume = 0.75
}
Not sure but these are the value for speech rate, try using them. I hope it helps.
iOS 9
Very Slow -- 0.42
Slower -- 0.5
My Normal -- 0.53
Faster -- 0.56
Value are different for previous versions of iOS, please keep that in mind while implementing the solution.

Resources