iOS Development With Swift Part 5
Contents
This is part 5 of the swift iOS Programming tutorials series. The first part of the series is here and the previous part of the series that is tutorial # 4 is here. In the last part we talked about segues and how to change the scene from the main view to another view. We also talked about view life cycle and what each function of view lifecycle does and where to write code. Now we have some basic understanding how to write code in our controller, how to add/position UI elements like buttons and labels on the view, how to add images and how to connect view with controller we can move on to the main part of our app which is to play audio. In this tutorial we will only play a hardcoded audio in our second controller and in the next part of this series we will tackle how to record and play the recorded audio.
Second Screen
In the first screen we will actually record user’s audio from iPhone and in the second scene after clicking on stop recording button we will play back the audio recorded from iPhone. Right now we will only focus on the second controller and only tackle on how we actually play an audio file and how to change speed of the audio. Let’s just jump in our code.
- Now first go to your storyboard and in the second screen for our new controller which is currently blank drag a button and drop it in our view.
- This button will be used to slow down the audio. We will add images to this button shortly but let’s first tackle how to talk to this button.
- What we want to do right now is to execute some code when the user clicks the button and we have already seen that the way to do that is to turn on assistant editor and control drag from the button to code to create an IBAction. Well, let’s think about it do we have a controller for the second screen? Where should we add the button? What is the view controller class for the second controller?
Adding Second View Controller
- To answer all the questions above right now we don’t have a view controller class like we have for our first scene. Our first scene view is connected to ViewController.swift that is why when we use assistant editor it automatically shows the ViewController.swift class but no such class right now exist for our second controller. You can check this by clicking on your first controller which has the record button and turning on assistant editor. Right now the first scene view is connected to ViewController.swift.
- Let’s fix this Go To File Menu in Xcode on the top toolbar and from there select New and select File. A new window should pop up. Make sure that in the left section under iOS you are in Source section.
- From the source section select Cocoa Touch Class and click next.
- Set the class name as PlaySoundsViewController.
- Set Subclass of to ViewController as this controller is like the first view controller.
- Leave other options as default. Your screen should like this screenshot
- Now click Next. Make sure that the file is saved in the folder for your project the same place ViewController.swift is saved and click Create.
Setting Second View Controller Up
Great you should see the new view controller file created but right now our scene is not connected to this controller. So let’s fix that.
- Go to storyboard. Select your second controller and make sure you select the yellow button on top of the controller. You can see what i mean here in this screenshot
- Now open up utilities area and go to the third tab there called Identity inspector. We have already worked on this tab. Make sure you have the yellow button selected in your second blank scene as visible in the last step screenshot.
- On top of the identity inspector you should see Custom Class section and there is a field named Class which is right now set to UIViewController. Right now this second scene is connected to UIViewController which is just a generic class and we can’t write code in that class. So click on the drop down in Class field and select PlaySoundsViewController
- Now hide the utilities inspector and show assistant editor. Select the new view we created in storyboard and you should see PlaySoundsViewController.swift code appear in the window on the right. (If PlaySoundsViewController.swift does not automatically appear make sure you have set the class correctly in storyboard and the automatic section is set to PlaySoundsViewController as you can see in this screenshot. If it is set to manual then change it to automatic. )
- Now try clicking on your first scene in storyboard and the controller should change back to ViewController.swift. Click again to second view controller to show PlaySoundsViewController code.
Adding Slow Down Audio Button
Now we are going to change the image to the button we added in our second scene to be snail image to slow down the rate of the audio, add constrains to position the button near the top left part of the screen, and add IBAction. We have already done these steps now at least 2-3 times so i will skip the detail and just list down the steps.
- First download this image(provided by Udacity instructor) to your computer.
- Go to Images.xcassets and add a new Image set by using the button at the buttom.
- Rename the name of the image set to Slow
- Find slow@2x image you downloaded and drag it to the 2x section in Slow image set.
- Go back to Xcode and show utilities area.
- Click on the button we have already placed in the second scene and select Attributes Inspector(4th tab) from utilities area. Empty the Title for the button.
- Set the image for the button to slow. The snail icon should show up.
- Now we are going to add constrains. Select the button control drag from button to top area and select the option Top Space to Top Layout Guide. Again control drag from button from button to the left and select Leading Space to Container Margin
- Select each constraint by selecting the button and clicking on the left and top lines and set the 40. For the left constraint set it to 20 (If you have trouble selecting the constraints lines then first show Document outline by pressing the button in bottom left of the screen in blue toolbar and select each constraints from there and set those values). You can view this screenshot to make sure you have placed the button in the correct place.
- Now hide utilities area and show assistant editor and select your second view on the left.
- Control drag from the slow button to your code and once you release select Action from the dropdown menu and select Type to UIButton and name to playSlowAudio.
- Now click connect and you should see your button. Put the following log in the playSlowAudio function to make sure that is it working.
1
println("Play Slow Audio")
- Now run the simulator and after clicking on the record button and clicking on stop on the second screen make sure that the button is in the correct position and click on it to make sure that Play Slow Audio appears in the console.
Audio Player
Now we need to find out how to actually play an audio file in iPhone. So far we have been dealing with UIKit as we have been working with UIButton and UILabel which are part of UIKit but to play audio we need to use AVFoundation framework. AVFoundation framework has a list of sub classes and one of the classes is the one we need.
- Have a look at AVFoundation documentation here and try to find the class which can help us play an audio file.
- Remember you want to look for a class in the link above which can play audio from file. You should be able to find the class fairly easily. Before i reveal to you what class we are looking for i want to remind you that documentation in XCode help and on apple website is best resource to find information about all classes in a framework, functions and properties of each class and detail about each class. If you are not able to find the answer in apple documentation then do a simple google search and you will probably find an article containing the answer you need to complete. Normally Stack Overflow is a good resource for finding posts related to your question.
- The class we are interested in AVAudioPlayer and the documentation says An instance of the
AVAudioPlayer
class, called an audio player, provides playback of audio data from a file or memory. You can learn about AVAudioPlayer from this link - We have found the class we need for playing audio but how do we use it? AVAudioPlayer apple documentation lists down the methods and attributes for the class but we need an example. Google AVAudioPlayer sample code and within the top results you will find this stackoverflow link and in the question you will find the code needed.
- The steps needed for playing audio is given in the code in the question in stackoverflow but there is one problem that the code is in objective-c which is an old and alternative way to write iOS code but we need code in swift language for our application.
- Even though you might not know objective-c just have a go and try to write down simple steps to play audio from the code provided. Once you have written down the steps have a look at the steps below and compare it with your answer.
- Get path to the audio file
- Create instance of audio player.
- Set path of the audio file in audio player.
- Play the file
Playing Audio File
Now we need to write down code to play the audio and somehow write equivalent swift code to objective-c code we found. Here are the steps to do that.
- Download this mp3 file provided by Udacity instructor which is from Forest Gump movie or you can use a very short mp3 file of your own.
- Go to PlaySoundsViewController.swift and add the following import code to include AVFoundation framework in our code in order to use AVAudioPlayer
1
import AVFoundation
- Now go to File Menu and click on Add File To Pitch Perfect. Select the mp3 file you downloaded. Make sure that in Destination section the tick box for Copy Items if needed is ticked so the audio file will be copied in your project directory and click add.
- Drag the mp3 file to supporting file folder from xcode.
- Now go to PlaySoundsViewController and in viewDidLoad enter the following the line after super.viewDidLoad()
1
var filePath = NSBundle.mainBundle().pathForResource("movie_quote", ofType: "mp3")
- You can read more about NSBundle from XCode by holding down option key and hovering over NSBundle and clicking on it. You can read the summary here or click on the link in the bottom for NSBundle Class Reference for more detail in documentation.
- Basically NSBundle is a way to point to a location to groups of code or resources which you can use in your code.
- mainBundle() within NSBundle returns the directory for your application. iPhone applications cannot read any other application directory directly and each application has an application from which that application can read or write resources. In our case the mp3 movie_quote.mp3 is present in our app directory which is returned by mainBundle.
- pathForResource actually looks for a file within our application directory for the file name and extension.
- After executing this code you should get the path of the movie_quote.mp3 file but there is a problem. The problem is that it is possible that the code we execute can return an invalid file or nil file. If you click on option key and click on pathForResource you will see it returns an optional. Look at the function definition in xcode documentation and you can see it returns a String optional.
1 2 3
class func pathForResource(_ name: String!, ofType extension: String!, inDirectory bundlePath: String!) -> String!
- We have talked about optionals before but in order to retrieve value back from an optional we need to use a different syntax then the code we have written. Basically the following code is a way to make sure that if there is no file then it prints an error otherwise it unwraps the optional and extract the path value from the optional and we can use that value in the if part.
1 2 3 4 5 6 7
if var filePath = NSBundle.mainBundle().pathForResource("movie_quote", ofType: "mp3") { } else { println("File not Found") }
- Now in the if part we need to write code to initialize AVAudioPlayer object. Go to help menu and click on documentation and API Reference menu item. Search for AVAudioPlayer and select the class.
- Scroll down and you should find a section labelled Initializing an AVAudioPlayer Object and have a look at the swift code. Basically we need to call init method on AVAudioPlayer and pass in the url to the file and error which will be nil for now but we can send an error object which can contain any error initializing AvAudioPlayer object.
- Another problem now we will face is that init method the url is type of NSURL whereas the file path we have is NSString. We need a way to convert NSString to NSUrl.
- Google this search term swift convert string to url and you should find this stack overflow article. Have a look at the accepted answer and you will find code which i have pasted below
let fileUrl = NSURL(fileURLWithPath: filePath)
- Add the following code in the if condition we added for filePath
1
NSURL.fileURLWithPath(filePath)
- Now add code the following code to initialize an object of audio player beneath filePathUrl code above in viewDidLoad
1
var audioPlayer = AVAudioPlayer(contentsOfURL: filePathUrl, error: nil)
- We have initialized an object of audio player. Now add this code to playSlowAudio
1
audioPlayer.play()
- When you enter the above command you will see that Xcode will tell you that there is an error and if you click on the red icon on the left of the code you will find error that Use of unresolve identifier audioPlayer. The reason for this error that we have declared audioplayer in our viewDidLoad and it’s scope is right now is only this function and we cannot right now use it outside of this function but we need access of this function outside of viewDidLoad in other functions as well.
- Solution to this problem is just above viewDidLoad function we can declare a member property which can be accessed everywhere in the class and we can initialzie this variable to audio player. So now just above the override func viewDidLoad() definition add this code. Also, in viewDidLoad remove the keyword var from audioPlayer
1
var audioPlayer:AVAudioPlayer!
- Now all your errors should disappear and your code should work properly as audioplayer is now available in whole class and initialized in viewDidLoad function. Now run your application and click on the snail button and you should the mp3 file now.
Rate of Audio
Nice we have played our audio but we really wanted to do was when we click on the snail button we want to play the audio file slower then it’s original speed. Let’s do that next.
- Read up on AVAudioPlayer documentation here and you try to find a function or property to the change speed/rate of the audio file.
- Scroll down and you should find a property called rate. Click on it and you should find that the normal rate is 1.0 and you can set rate minimum to 0.5 to slow down audio and maximum upto 2.0 to speed up audio. Before we can make this change also note that we have to set enableRate property to true first to change rate.
- Now go to viewDidLoad and after intializing audio player object add code to enableRate. Your viewDidLoad right now should like the following code.
super.viewDidLoad()
1 2 3 4 5 6 7 8 9 10 11 12 13
if var filePath = NSBundle.mainBundle().pathForResource("movie_quote", ofType: "mp3") { var filePathUrl = NSURL.fileURLWithPath(filePath) audioPlayer = AVAudioPlayer(contentsOfURL: filePathUrl, error: nil) audioPlayer.enableRate = true } else { println("File not Found") }
- Now go to playSlowAudio function and before playing the audio add the code below to change rate of the audio.
1
audioPlayer.rate = 0.5
- Now run your application and click on snail and the audio should play in slower rate now.
Adding Fast Audio Button
Now we need another button to play the audio in faster speed. The procedure is similar to slow down audio button so i will only list down smaller instructions.
- Download the fast audio button image
- Adding the image to xcode image assets.
- Go to storyboard drag a button and align it on the right side of the slow button.
- Blank out text and set the image to fast button
- Add constrains to set the distance to top guide to 40 and to right container to 20 as well.
- Add an action for this button and give it name playFastAudio
- Write the same code in playSlowAudio in playFastAudio with one change that set rate to 2.0 in playFastAudio function to speed up the audio.
- Test it out.
Improving Code
- Now let’s add a stop button like i did in first screen to stop any audio from playing.
- The image for the stop button is the same we used in the first screen so drag a button and change image to stop button in second screen and add constrains to position it in the middle and at a fixed distance from the bottom of the screen.
- Add action to your controller and in that call stop function for audioPlayer object in that function as following
1
audioPlayer.stop()
- Also add the same code in both slow and fast functions in the start to make sure that any running audio is stopped before running new audio
- Run the app and click on any button and before the audio finishes playing click on the stop button to stop audio playing. This should work but there is a problem that whenever you stop the audio using stop button and then click on any other button then you will see that instead of playing the audio file from the start it resumes at the same position the audio was stopped with. We need to reset the start position. Add the following code in both slow and fast audio functions after stop function is called to fix this problem and reset the position of the audio
1
audioPlayer.currentTime = 0.0
- This should fix your problem but compare code for both slow and fast audio there is only one change for rate otherwise both functions have the same code so let’s refactor this code into another function and pass in the rate from each function to play the correct speed of audio.
- First declare a new function named playAudio with a parameter rate as following
1
func playAudio(rate: Float) { }
- Now copy the code from playSlowAudio to playAudio and change the line for audioPlayer.rate = 0.5 to audioPlayer.rate = rate. Your final code for the function should be as following
-
1 2 3 4 5 6 7 8 9 10 11
func playAudio(rate: Float) { audioPlayer.stop() audioPlayer.rate = rate audioPlayer.currentTime = 0.0 audioPlayer.play() }
- From playSlowAudio remove the code you copied over to playAudio and call playAudio with a value of 0.5 like playAudio(0.5) and in fast audio function make the same change with value of 2.0 and run your code and your code should work and you have factored out common code in a separate manageable function.
We have been able to play an audio file and play it at different rates and add a stop button as well. In the next part of this series we will we add code to record audio from the device and use the recorded audio rather than hard coded audio in the next screen and finally in the last part of the series we will look into how to make the audio file sound like chipmunk or darth vader and also how to run the application on a physical device.
The next part of this series can be found here