How to make slider value change object or function? - ios

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))
}

Related

How to get the current page number of a PDF?

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;
}
}

Change AVSpeechUtterance rate in real time

I am currently developing an iOS app that converts text to speech using AVSynthesizer.
What I want to do is that while the synthesizer is speaking, utterance rate can be changed and with a slider and the speed of the speaking changes.
I am doing this in the IBAction of the slider:
self.utterance = sender.value
but the synthesizer doesn't change the speed. I've been looking for information but I haven't found something yet.
What cand I do? Thanks in advance.
Ok, so after playing a bit with this cool feature I wasn't aware of, I found out a way to change the rate of utterance. The main problem is that the utterance is currently enqueued by synthetizer, the rate can't be changed. Corresponds to the documentation:
/* Setting these values after a speech utterance has been enqueued will have no effect. */
open var rate: Float // Values are pinned between AVSpeechUtteranceMinimumSpeechRate and AVSpeechUtteranceMaximumSpeechRate.
open var pitchMultiplier: Float // [0.5 - 2] Default = 1
open var volume: Float // [0-1] Default = 1
So the workaround would be to stop the synthesizer and feed him new utterance with trimmed string.
import UIKit
import AVFoundation
class ViewController: UIViewController {
var synthesizer: AVSpeechSynthesizer!
var string: String!
var currentRange: NSRange = NSRange(location: 0, length: 0)
#IBAction func sl(_ sender: UISlider) {
synthesizer.stopSpeaking(at: .immediate)
if currentRange.length > 0 {
let startIndex = string.index(string.startIndex, offsetBy: NSMaxRange(currentRange))
let newString = string.substring(from: startIndex)
string = newString
synthesizer.speak(buildUtterance(for: sender.value, with: string))
}
}
func buildUtterance(for rate: Float, with str: String) -> AVSpeechUtterance {
let utterance = AVSpeechUtterance(string: str)
utterance.rate = rate
utterance.voice = AVSpeechSynthesisVoice(language: "en-US")
return utterance
}
override func viewDidLoad() {
super.viewDidLoad()
string = "I am currently developing an iOS app that converts text to speech using AVSynthesizer.What I want to do is that while the synthesizer is speaking, utterance rate can be changed and with a slider and the speed of the speaking changes. I am doing this in the IBAction of the slider: self.utterance = sender.value but the synthesizer doesn't change the speed. Ive been looking for information but I haven't found something yet. What can I do? Thanks in advance."
synthesizer = AVSpeechSynthesizer()
synthesizer.delegate = self
synthesizer.speak(buildUtterance(for: AVSpeechUtteranceDefaultSpeechRate, with: string))
}
}
extension ViewController: AVSpeechSynthesizerDelegate {
func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, willSpeakRangeOfSpeechString characterRange: NSRange, utterance: AVSpeechUtterance) {
debugPrint(characterRange.toRange())
currentRange = characterRange
}
}
implement the delegate method willSpeakRangeOfSpeechString of AVSpeechSynthesizerDelegate and define the delegate of synthesizer as self: synthesizer.delegate = self
In that delegate method, save the characterRange which would be spoken next.
Bind IBAction func sl(_ sender: UISlider) to your slider's event touchUpInside.
in that IBAction, stop speaking, and get the substring of your current text being spoken from index it would've continue.
Build new utterance and start speaking it
Profit.
swift 3
import UIKit
import AVFoundation
class ViewController: UIViewController{
#IBOutlet weak var sliderVolume: UISlider! //for volume
#IBOutlet weak var sliderRate: UISlider! //for rate
#IBOutlet weak var sliderPitch: UISlider! //for pitch
#IBOutlet weak var txtForSpeak: UITextField!
let speechSynth = AVSpeechSynthesizer()
#IBAction func btnStartToSpeak(_ sender: UIButton) {
let speechUtt = AVSpeechUtterance(string: self.txtForSpeak.text!)
speechUtt.rate = self.sliderRate.value
speechUtt.volume = self.sliderVolume.value
speechUtt.pitchMultiplier = self.sliderPitch.value
self.speechSynth.speak(speechUtt)
}
}

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.

Flow Control in Swift - AVAudioPlayer

I wrote an Iphone Swift app that plays a series of sounds in a random order using AVAudioPlayer-- for now a pop sound, a horn sound and a gong. It works when you hit the play button, except....
However, when I hit the stop button nothing happens- it doesn't respond (The stop does work if I have just one sound). I believe it is due to my flow control. If I did not put in the 'while soundPlayer.playing == true {}', the code would "fly" through the sound and not wait for it to finish.
How can I modify the code so the the sound plays to completion before going to the next sound? And also allow the stop button to be functional? See Code and screen shot below. (Actually Stack overflow will not allow me to post an image since I am so new)
//
// ViewController.swift
// InsultSchool2 Swift
//
// Created by JR on 12/3/14.
// Copyright (c) 2014 JR. All rights reserved.
//
import UIKit
import AVFoundation
//---------------------------------------------------
var soundPlayer:AVAudioPlayer = AVAudioPlayer()
// used to try to do some kind of flow control. May not be needed if better way is found.
var stopFlag:Bool = false
//-----------------------------------------------------
class ViewController: UIViewController {
#IBOutlet weak var valueSliderTime: UISlider!
#IBOutlet weak var valueLabelTime: UILabel!
#IBOutlet weak var valueStepperVolume: UIStepper!
#IBOutlet weak var valueLabelVolume: UILabel!
//------------------------------------------------------
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//Displays an initial Volume value in volume label on load
//Convert to an int. Otherwise you get a weird value when getting close to zero
//Multiply by 10 so the int works. Otherwise you would int'ing a number between 0.0 and 1.0.
// "\()" is a shorthand to convert whatever to a string
valueLabelVolume.text = "\(Int(valueStepperVolume.value * 10))"
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//------------------------------------------------------
#IBAction func buttonStop(sender: UIBarButtonItem) {
NSLog("Enter Button Stop")
//?1 If a sound is not playing and stop is hit, then it crashes
//?2 the stop button does not work with the whlle loop below
soundPlayer.stop()
}
#IBAction func sliderTime(sender: UISlider) {
valueLabelTime.text = "\(Int(valueSliderTime.value))"
}
#IBAction func stepperVolume(sender: UIStepper) {
//Converted to an int. Otherwise you get a weird value when getting close to zero
valueLabelVolume.text = "\(Int(valueStepperVolume.value * 10))"
}
#IBAction func buttonPlay(sender: UIBarButtonItem) {
NSLog("Enter Button Start")
var soundArray:[String] = ["Sound0", "Sound1", "Sound2"]
// Randomizes a number to indicate which random sound to play in the array
/* Number is from 0 to number in the (). Don't add one or 0 will never play. Go one more than the numbers in the array. For example if you have 3 items in the array go to 3. THis will go from 0 to 2 (ie., 3 items)*/
// Reference----- var soundRandomNumber:Int = Int(arc4random_uniform(3))
var soundRandomNumber:Int
soundRandomNumber = Int(arc4random_uniform(3))
//Creates a random number to wait between sounds based on the slider value.
//arc4random requires a UInt32 (Unsigned is a positive number).
//_uniform is slightly more random than without the Uniform
//The documentation says to use Int otherwise.
println(Int(valueSliderTime.value))
NSLog("valueSliderTime.value")
var waitTimeRandomNumber = Int(arc4random_uniform(UInt32(valueSliderTime.value)))
println(waitTimeRandomNumber)
NSLog("waitTimeRandomNumber")
// Constructs a string with the random number for the URL
//var soundFile:String = soundArray[soundRandomNumber]
var soundFile:String
soundFile = soundArray[soundRandomNumber]
//Reference---- var soundURL = NSBundle.mainBundle().URLForResource(soundFile, withExtension:"mp3")
var soundURL:NSURL!
soundURL = NSBundle.mainBundle().URLForResource(soundFile, withExtension:"mp3")
soundPlayer = AVAudioPlayer(contentsOfURL: soundURL, error: nil)
//?3 How do I set up a loop or control that works until the stop button is pressed?
while stopFlag == false{
NSLog("inside while")
println(stopFlag)
//?4 Is the below correct? The actual volume does not seem to change though the .volume does
soundPlayer.volume = Float(valueStepperVolume.value)
println(Float(valueStepperVolume.value))
NSLog("Float(valueStepperVolume.value)")
println(soundPlayer.volume)
NSLog("soundPlayer.volume")
soundRandomNumber = Int(arc4random_uniform(3))
soundFile = soundArray[soundRandomNumber]
soundURL = NSBundle.mainBundle().URLForResource(soundFile, withExtension:"mp3")
soundPlayer = AVAudioPlayer(contentsOfURL: soundURL, error: nil)
soundPlayer.prepareToPlay()
soundPlayer.play()
//?5 How do I make the player not blow through the sound and wait until is finished
while soundPlayer.playing == true {
}
//?6 How can i make a random timer that waits for a random time before relooping?
waitTimeRandomNumber = Int(arc4random_uniform(UInt32(valueSliderTime.value)))
}// End of while loop
} //ends playButton IBAction
//?7 How to allow this app to play over other music in another player
}
use the repeat while statement instead to control the flow:
here is a link to apple's developers reference:
https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ControlFlow.html
repeat {
// move up or down for a snake or ladder
square += board[square]
// roll the dice
if ++diceRoll == 7 { diceRoll = 1 }
// move by the rolled amount
square += diceRoll
} while square < finalSquare
print("Game over!")

NSTimeInterval as a countdown timer, applying it as a seperate reusable class

EDIT: I'm an idiot. I was trying to put the variable into NSTimer() and not NSTimeInterval for whatever silly reason. I guess my question is how can I wrap this all up in a separate class?
Ideally, I'd like all this wrapped up in a separate class (CountdownTimer), so I can create new instance of a timer but still retain all the functionality that NSTimer includes such as the ability to check timer.isValid. Psuedocode would look something like:
var timer = CountdownTimer(countDownFrom: 300)
timer.start()
timer.isValid()
My UIViewController class (not in viewDidLoad):
var totalCountDownTimeInterval = NSTimeInterval(480.0)
var startTime = NSDate()
var timer = NSTimer()
var isRunning = false
func updateTime() {
var elapsedTime : NSTimeInterval = NSDate().timeIntervalSinceDate(startTime)
var remainingTime : NSTimeInterval = totalCountDownTimeInterval - elapsedTime
if remainingTime <= 0.0 {
timer.invalidate()
}
let minutes = UInt8(remainingTime / 60.0)
remainingTime = remainingTime - (NSTimeInterval(minutes) * 60)
let seconds = UInt8(remainingTime)
println("The time is \(minutes) and \(seconds)")
}
#IBOutlet weak var TimerCount: UILabel!
#IBAction func StartButton(sender: AnyObject) {
if !timer.valid {
startTime = NSDate()
let aSelector : Selector = "updateTime"
timer = NSTimer.scheduledTimerWithTimeInterval(0.10, target: self, selector: aSelector, userInfo: nil, repeats: true)
}
}
#IBAction func StopButton(sender: AnyObject) {
timer.invalidate()
}
#IBAction func ResetButton(sender: AnyObject) {
timer.invalidate()
TimerCount.text = "00:00"
}
Make sure that you're actually passing in a double. I like to explicitly state the type of my variables when I declare them; it helps to avoid problems just like this.
You're most likely declaring doubleValue like this:
let doubleValue = 480
instead of like this:
let doubleValue = 480.0
or like this:
let doubleValue: Double = 480
If you've declared your variable correctly, this should work:
let timeInterval = NSTimeInterval(doubleValue)
If you are going to let the compiler infer the variable's type, just make sure that whatever's on the right side of your assignment operator evaluates to the type you're looking for. 480 evaluates to Int(480) while 480.0 evaluates to Double(480).
EDIT: Here's the answer to your second question: How can I wrap this [timer functionality] up in a separate class?
It's actually really simple. Assuming that all you want to do with the class is to be able to start it and check if it's still valid, here's how I would go about doing this:
class CountdownTimer
{
var time: NSTimeInterval
private var startTime: NSDate?
init(countDownFrom timeInSeconds: Int)
{
time = NSTimeInterval(timeInSeconds)
}
func start()
{
startTime = NSDate()
}
func isValid() -> Bool
{
if (startTime != nil)
{
let timePassed: NSTimeInterval = -(startTime!.timeIntervalSinceNow)
return timePassed < time
}
else
{
return false
}
}
}
Now, be warned, I barely tested this. Playground isn't complaining and from the looks of it, this should work. Now, just use the class like so:
var myCountdownTimer = CountdownTimer(countDownFrom: 300)
// and then whenever we want to start the countdown:
myCountdownTimer.start()
// and then whenever we want to check if the clock's still "ticking", so to speak:
myCountdownTimer.isValid()
// and if we want to restart the timer:
myCountdownTimer.time = NSTimeInterval(900) // we can change the time if we want
myCountdownTimer.start()
Essentially, all a CountdownTimer object does is save the exact time start() is called to a variable called startTime. Note that NSDate() by default is set to the time-and-date it's created. Then isValid() simply checks to see if the timePassed is less than whatever time the timer was set to count down from; if it is, then it returns true.
I tried this code on playground of Xcode 6.1, And It worked fine.
That's strange...
let someValue: Double = 60.0
var timeInterval = NSTimeInterval(someValue)
println(timeInterval)

Resources