Switches: class UISwitch

Press the switch and hear background music. See Switches in the Human Interface Guidelines.

Source code in Switch.zip

  1. Class AppDelegate: unchanged.
  2. Class ViewController has all the audio code. Added the init(coder:) whose parameter is an NSCoder, and the valueChaged(:) method.
  3. Class View has the objects we see on the screen. Added the init(coder:) whose parameter is an NSCoder.
  4. Assets.xcassets
    1. Contents.json
    2. musette.dataset
      1. Contents.json
      2. musette.mp3
  5. Info.plist

Create the project

Class View is a subclass of UIView. The file ViewController.swift has to import the AVFoundation framework before it can mention the word AVAudioPlayer.

MIDI was not on the list of supported formats, so I converted the file musette.mid to MP3 with this converter. Then I added the resulting file musette.mp3 to the project by putting it into Assets.xcassets as we added the photo in America.

The UISwitch control

See Switches in the UIKit User Interface Catalog. The word switch is a keyword in Swift (and in Objective-C), so I had to name the switch mySwitch in View.swift and ViewController.swift.

The UIButton in Button could be given any desired size. But a UISwitch has a preferred size, 51 × 31 points on iPhone 6. To discover these dimensions, uncomment the drawRect(_:) method of the View and insert the following statements. The dimensions of the View on iPhone 6s Plus are 414 × 736 points, which explains where the x, y coördinates come from (approximately): (414 − 51) / 2 = 181.5 and (736 − 31) / 2 = 352.5

		print("frame = \(frame)");
		//The switch was the last subview added to the view.
		let mySwitch: UISwitch = subviews[subviews.count - 1] as! UISwitch;
		print("mySwitch.frame = \(mySwitch.frame)");
frame = (0.0, 0.0, 414.0, 736.0)
mySwitch.frame = (181.333333333333, 352.333333333333, 51.0, 31.0)
We will stay with this preferred size, even though we could change it with the CGAffineTransformMakeScale we saw in Animate.
		//We elect not to do this.
		mySwitch.transform = CGAffineTransformMakeScale(0.5, 0.5);

I don’t want to hardcode the numbers 51 × 31 into the app, because Apple might change them in the future. I therefore created the mySWitch property without specifyings its size or position. Then the init(coder:) method of class View centers the switch in the View and keeps it there.

As usual, the target for the switch is designated with the method addTarget(_:action:forControlEvents). In this case, the target is the ViewController object. The application delegate concerns itself with lifecycle events; the view object concerns itself with what you see on the screen. The view controller therefore receives the audio code by process of elimination. We call the valueChanged(_:) method of the view controller when the switch changes from off to on or vice versa.

path = /Users/myname/Library/Developer/CoreSimulator/Devices/4248FDA4-DE7B-4D16-8A71-4BBF066C98F4/data/Containers/Bundle/Application/1CD514C6-9E56-4D59-9743-ACC712059E6D/Switch.app/musette.mp3

url = file:///Users/myname/Library/Developer/CoreSimulator/Devices/4248FDA4-DE7B-4D16-8A71-4BBF066C98F4/data/Containers/Bundle/Application/1CD514C6-9E56-4D59-9743-ACC712059E6D/Switch.app/musette.mp3

player!.numberOfChannels = 2
Playing at 0.0 of 25.0004535147392 seconds.
Paused at 3.88814058956916 of 25.0004535147392 seconds.
Playing at 3.91031746031746 of 25.0004535147392 seconds.
Paused at 7.84843537414966 of 25.0004535147392 seconds.

The AVAudioPlayer and its documentation

Use the System Sound Services functions in Button to play a short sound (a warning, beep, explosion, Chinese sound effect, etc). Use an AVAudioPlayer object to play a long sound (Bach, Beethoven, Brahms). The ViewController is the target of the switch and will be the delegate of the audio player.

Johann Sebastian Bach (1685–1750)

The Musette in D Major (BWV Anhang 126) from the Notebook for Anna Magdalena Bach was the soundtrack for the holiday light show a few years back in Grand Central Terminal. PDF. MIDI.

Music preview

Things to try

  1. Remove the infinite loop. Play the file once and then stop:
    		player!.numberOfLoops = 0; //No loops.  Just play it once.
    

  2. Now that we’re playing the file only once, put the switch back in the off position when the file has finished playing. In the file ViewController.swift, let the view controller adopt the AVAudioPlayerDelegate protocol.
    class ViewController: UIViewController, AVAudioPlayerDelegate {
    
    The view controller is now qualified to act as the delegate of the audio player. Insert the following statement into the code that configures the audio player.
    		//Let the view controller be the delegate of the audio player.
    		player!.delegate = self;
    
    Let mySwitch be a stored property of class View instead of a variable inside of init(coder:). Add the following method to class ViewController in the file ViewController.swift. Also try it with animation turned off; you’ll prefer to turn it back on again.
    	//This method of the audio player's delegate object is called
    	//automatically at the end of the sound file.
    
    	func audioPlayerDidFinishPlaying(player: AVAudioPlayer, successfully flag: Bool) {
    		//Go back to the off position.
    		let mySwitch: UISwitch = (view as! View).mySwitch;
    		mySwitch.setOn(false, animated: true);
    	}
    

    The conscientious programmer will also give the method audioPlayerDecodeErrorDidOccur:error: to the audio player delegate.


  3. Print the player’s dictionary of settings that describe the sound file. Insert the following code into the init(coder:) method of the view controller immediately after the existing
    		if !player!.prepareToPlay() {
    			print("player!.prepareToPlay failed");
    		}
    
    The format ID value is 778,924,083 in decimal and 2E6D7033 in hexadecimal. The four bytes 2E, 6D, 70, 33 are the ASCII codes of the four characters ".mp3". The 8 is the number of bits in a Character; I could have said Int(CHAR_BIT) instead of 8. The 3 is one less than the number of bytes in an Int. Why is the sample rate a perfect square (44,100 = 2102)?
    			let settings: [String: AnyObject]! = player!.settings;
    
    			for (key, value) in settings {
    				if key == AVFormatIDKey {
    					let s: Int = value as! Int;
    					var format: String = "";
    					for i in 3.stride(through: 0, by: -1) {
    						format += String(UnicodeScalar(s >> (i * 8) & 0xFF));
    					}
    					print("\(key): \(format)");
    				} else {
    					print("\(key): \(value)");
    				}
    			}
    
    AVEncoderBitRateKey: 0
    AVNumberOfChannelsKey: 2
    AVFormatIDKey: .mp3
    AVSampleRateKey: 44100
    

  4. Why do the AVAudioPlayer in this app, the MPMoviePlayerController in Video, and the AudioServicesPlaySystemSound function in Button use such totally different mechanisms to inform the rest of the app that the media file has finished playing?