Saving data with User defaults - ios

I am making a feature in my app that allows you to mute the music, i put a switch there so you can change it between on and off. However, the switch works fine in the app, when i close the vie controller and i pull it back up it will still show it how it was left, but when you close the app and open it back up the switch looks as if it off but it still plays the music.This is my code and i will attach a picture.
import Foundation
import UIKit
import SpriteKit
import AVFoundation
var bombSoundEffect: AVAudioPlayer!
var Ghost = SKSpriteNode()
class SecondViewController: UIViewController {
var sw = false
#IBOutlet var mySwitch: UISwitch!
#IBAction func switchpressed(_ sender: AnyObject) {
let defaults = UserDefaults.standard
if mySwitch.isOn{
defaults.set(true, forKey: "SwitchState")
if bombSoundEffect != nil {
bombSoundEffect.stop()
bombSoundEffect = nil
}
}
else{
defaults.set(false, forKey: "SwitchState")
let path = Bundle.main.path(forResource: "newmusic.wav", ofType:nil)!
let url = URL(fileURLWithPath: path)
do {
let sound = try AVAudioPlayer(contentsOf: url)
bombSoundEffect = sound
sound.numberOfLoops = -1
sound.play()
} catch {
// couldn't load file :(
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
super.viewDidLoad()
let defaults = UserDefaults.standard
if (defaults.object(forKey: "SwitchState") != nil) {
mySwitch.isOn = defaults.bool(forKey: "SwitchState")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Release any cached data, images, etc that aren't in use.
}
}
and here is the picture
If you didn't understand what i said above please ask questions.

As i understand from your code, you are not checking the User Defaults properly. In your viewDidLoad you have to check the bool value of your user defaults is set to true or not.
override func viewDidLoad() {
super.viewDidLoad()
let defaults = UserDefaults.standard
//check bool Value is set to true or not
if defaults.bool(forKey: "SwitchState") == true {
mySwitch.isOn = defaults.bool(forKey: "SwitchState")
} else {
print("false")
}
}
Please check this code. Hopefully it will helps you.

In viewDidLoad you need to check for when you need to pause the music:
// Keep this part the same
let defaults = UserDefaults.standard
if (defaults.object(forKey: "SwitchState") != nil) {
mySwitch.isOn = defaults.bool(forKey: "SwitchState")
}
// Check to see if the switch is off, if so, stop the music
if !mySwitch.isOn {
bombSoundEffect.stop()
bombSoundEffect = nil
}
You can also take out the duplicate call to super.viewDidLoad.
Note: It really looks like there is more code in your view controller, because there isn't anything currently there that would be playing the music, so this solution may not work given that you haven't provided enough information.

Related

How to turn off Background Music using a Toggle Switch in Xcode using Swift?

I am playing background music in my app from the App delegate once the app launches. Now, In my 2nd V.C. I have set up a toggle switch to turn om/off the background music. But, whenever I am running the follwing code, my app is crashing giving me this error:-
Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
Could anyone please let me know how can I implement the following task in my V.C. Would appreciate your help! Thanks:)
**App Delegate**
let vc = SecondViewController()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
vc.playMusic()
return true
}
**Second View Controller**
import UIKit
import AVFoundation
class SecondViewController: UIViewController {
#IBOutlet weak var musicToggleSwitch: UISwitch!
var music: AVAudioPlayer!
let vc_1 = ViewController()
override func viewDidLoad() {
super.viewDidLoad()
self.musicToggleSwitch.setOn(UserDefaults.standard.bool(forKey: "musicToggleState"), animated: true)
}
#IBAction func musicToggleSwitch(_ sender: UISwitch) {
if (musicToggleSwitch.isOn == true) {
if (music.isPlaying == false) {
music.play()
}
}
else {
if (music.isPlaying == true) {
music.stop()
}
}
UserDefaults.standard.set(sender.isOn, forKey: "musicToggleState")
}
func playMusic() {
if let musicURL = Bundle.main.url(forResource: "Music", withExtension: "mp3") {
if let audioPlayer = try? AVAudioPlayer(contentsOf: musicURL) {
music = audioPlayer
music.numberOfLoops = -1
music.play()
}
}
}
}
This should solve your issues.
If it still doesn't play, add some breakpoints in the creation block of the music variable to see what goes wrong.
class SecondViewController: UIViewController {
#IBOutlet weak var musicToggleSwitch: UISwitch!
var music: AVAudioPlayer? = {
guard let musicURL = Bundle.main.url(forResource: "Music", withExtension: "mp3") else {
return nil
}
let audioPlayer = try? AVAudioPlayer(contentsOf: musicURL)
audioPlayer?.numberOfLoops = -1
return audioPlayer
}()
override func viewDidLoad() {
super.viewDidLoad()
self.musicToggleSwitch.setOn(UserDefaults.standard.bool(forKey: "musicToggleState"), animated: true)
}
#IBAction func musicToggleSwitch(_ sender: UISwitch) {
guard let music = music, sender.isOn != music.isPlaying else {
return
}
if sender.isOn {
music.play()
} else {
music.stop()
}
UserDefaults.standard.set(sender.isOn, forKey: "musicToggleState")
}
func playMusic() {
music?.play()
}
}
The problem is this line:
let vc = SecondViewController()
That is not the same SecondViewController that you see in your interface. So playMusic is called on the wrong view controller, and on the view controller you can see music remains nil and you crash.

How to save the state of an UISwitch in SWIFT 4? [duplicate]

This question already has answers here:
How do i keep UISwitch state when changing ViewControllers?
(3 answers)
Closed 5 years ago.
I would like to save the State of an UISwitch after to change between View Controllers. Any help would be greatly appreciated!
I have a first View Controller with an UISwitch, to control the music in the background in different View Controllers:
#IBOutlet weak var SwitchMusic: UISwitch!
let defaults = UserDefaults.standard
var switchON : Bool = false
#IBAction func checkState(_ sender: UISwitch) {
if (sender.isOn == true)
{
switchON = true
defaults.set(switchON, forKey: "switchON")
MusicHelper.sharedHelper.playBackgroundMusic()
}
if (sender.isOn == false)
{
switchON = false
defaults.set(switchON, forKey: "switchON")
MusicHelper.sharedHelper.stopBackgroundMusic()
}
}
And a Second View Controller to load or no the music in the background if the switch is On or Off:
override func viewDidLoad() {
super.viewDidLoad()
if defaults.value(forKey: "switchON") != nil{
let switchON: Bool = defaults.value(forKey: "switchON") as! Bool
if switchON == true{
MusicHelper.sharedHelper.playBackgroundMusic()
}
else if switchON == false{
MusicHelper.sharedHelper.stopBackgroundMusic()
}
}
}
Also I have a class with the music:
class MusicHelper {
let defaults = UserDefaults.standard
static let sharedHelper = MusicHelper()
var musicBackgroundIntro:AVAudioPlayer = AVAudioPlayer()
func playBackgroundMusic() {
do {
let audioPath = Bundle.main.path(forResource: "Music", ofType: "mp3")
try musicBackgroundIntro = AVAudioPlayer(contentsOf: NSURL(fileURLWithPath: audioPath!) as URL)
musicBackgroundIntro.numberOfLoops = -1
musicBackgroundIntro.prepareToPlay()
musicBackgroundIntro.play()
} catch {
print("Cannot play the file")
}
}
func stopBackgroundMusic() {
musicBackgroundIntro.stop()
}
}
Now it is working perfectly the music in the background between View Controllers, and it is possible to turn off and on... but unfortunately do not save the current state of the UISwitch, and always when I enter in the First View Controller the state of the Switch is On.
Also any idea that how will be possible to apply in a Slider too?
Any help would be greatly appreciated!
Thanks
The easiest way for you would be to create a static var isSwitchOn: Bool = false
This state will be preserved between back and forth transitions.
You should reflect the state, if music is playing...
class MusicHelper {
public isPlaying: Bool {
get {
return musicBackgroundIntro.isPlaying
}
}
// your stuff here..
}
That way in other view controllers:
SwitchMusic.isOn = MusicHelper.sharedHelper.isPlaying
If you need other view controllers to update in response to this, you can add a delegate event (aka observer) if necessary.
Try something like that: Use the UISwitch as an #IBOutlet.
#IBOutlet weak var checkState: UISwitch!
override func viewDidLoad() {
super.viewDidLoad()
self.checkState.addTarget(self, action: #selector(action(sender:)), for: .valueChanged)
}
// Save state
func action(sender: UISwitch) {
let userDefaults = UserDefaults.standard
userDefaults.set(sender.isOn, forKey:"identifier")
}
// Retrieve state
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let userDefaults = UserDefaults.standard.bool(forKey: "identifier")
self.checkState.setOn(userDefaults, animated: false)
}
You can give the switch a default value when it's created in viewcontroller1.
Assign the (default.value(forKey:"switchOn") as! Bool ) ?? false to that switch.

Segmentation fault: 11 with swift

I am experimenting with my code to try to add some new features in. After adding in the new UserDefaults I got an error that read "Segmentation fault: 11" and it flagged my secondviewcontoller, the code is down below. Please help
import Foundation
import UIKit
import SpriteKit
import AVFoundation
protocol DestinationViewDelegate {
}
var delegate : GameViewDelegate! = nil
var bombSoundEffect: AVAudioPlayer!
var ghost = SKSpriteNode()
class SecondViewController: UIViewController, GameViewDelegate {
var sw = false
let defaults = UserDefaults.standard
#IBAction func one(_ sender: AnyObject) {
defaults.set(1, forKey: "Sphere")
print("Ghost one was selected")
}
#IBAction func two(_ sender: AnyObject) {
defaults.set(2, forKey: "Sphere")
print("Ghost two was selected")
}
#IBAction func three(_ sender: AnyObject) {
defaults.set(3, forKey: "Sphere")
print("Ghost three was selected")
}
#IBAction func four(_ sender: AnyObject) {
defaults.set(4, forKey: "Sphere")
print("Ghost four was selected")
}
#IBAction func five(_ sender: AnyObject) {
defaults.set(5, forKey: "Sphere")
print("Ghost five was selected")
}
#IBAction func six(_ sender: AnyObject) {
defaults.set(6, forKey: "Sphere")
print("Ghost six was selected")
}
#IBOutlet var mySwitch: UISwitch!
#IBAction func switchpressed(_ sender: AnyObject) {
let defaults = UserDefaults.standard
if mySwitch.isOn{
defaults.set(true, forKey: "SwitchState")
if bombSoundEffect != nil {
bombSoundEffect.stop()
bombSoundEffect = nil
}
}
else{
defaults.set(false, forKey: "SwitchState")
let path = Bundle.main.path(forResource: "Untitled2.wav", ofType:nil)!
let url = URL(fileURLWithPath: path)
do {
let sound = try AVAudioPlayer(contentsOf: url)
bombSoundEffect = sound
sound.numberOfLoops = -1
sound.play()
} catch {
// couldn't load file :(
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Keep this part the same
let defaults = UserDefaults.standard
if (defaults.object(forKey: "SwitchState") != nil) {
mySwitch.isOn = defaults.bool(forKey: "SwitchState")
}
// Check to see if the switch is off, if so, stop the music
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Release any cached data, images, etc that aren't in use.
}
}
The problem is this bit of code:
#IBAction func one(_ sender: AnyObject) {
defaults.set(1, forKey: "Sphere")
print("Ghost one was selected")
}
You've found a compiler bug. Try to work around it like this:
#IBAction func one(_ sender: AnyObject) {
defaults.set(1 as Any, forKey: "Sphere")
print("Ghost one was selected")
}
You will need to do that for all your defaults.set calls. I think that will allow you to compile.

How to check the state of an UISwitch from another ViewController in SWIFT 3?

I want to check the State of an UISwitch from another View Controller. And if the switch is on, then I want the backgroundColor of the View to be red, otherwise it should be green. I tried to use UserDefaults, but I have no idea what to start with. Any help would be greatly appreciated!
NOTE: THIS IS THE VERSION FOR SWIFT 3:
First of all, I've set the initial state of the switch to off. I have added a button, which leads us to the secondViewController and also checks the state of the switch. Here is my code(for the View Controller which contains the switch):
#IBOutlet weak var switch1: UISwitch!
let defaults = UserDefaults.standard
var switchON : Bool = false
#IBAction func checkState(_ sender: AnyObject) {
if switch1.isOn{
switchON = true
defaults.set(switchON, forKey: "switchON")
}
if switch1.isOn == false{
switchON = false
defaults.set(switchON, forKey: "switchON")
}
}
And for the secondView:
let defaults = UserDefaults.standard
override func viewDidLoad() {
super.viewDidLoad()
if defaults.value(forKey: "switchON") != nil{
let switchON: Bool = defaults.value(forKey: "switchON") as! Bool
if switchON == true{
self.view.backgroundColor = UIColor.red()
}
else if switchON == false{
self.view.backgroundColor = UIColor.green()
}
}
}

recording audio in swift and passing the recorded audio to the next view controller

I am trying to record Audio, and pass the recorded audio, to the next view controller. Here is my code for recording Audio
class RecordSoundsViewController: UIViewController, AVAudioRecorderDelegate {
#IBOutlet weak var recording: UILabel!
#IBOutlet weak var recordButton: UIButton!
#IBOutlet weak var stopButton: UIButton!
var audioRecorder:AVAudioRecorder!
var recordedAudio : RecordedAudio!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(animated: Bool) {
// enables record button
// hides the stop button
recordButton.enabled = true
stopButton.hidden = true
}
#IBAction func recordAudio(sender: UIButton) {
//Shows recording label
recording.hidden = false
//diabling record button
recordButton.enabled = false
stopButton.hidden = false
//Filepath Creation
let dirPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as! String
let currentDateTime = NSDate()
let formatter = NSDateFormatter()
formatter.dateFormat = "ddMMyyyy-HHmmss"
let recordingName = formatter.stringFromDate(currentDateTime)+".wav"
let pathArray = [dirPath, recordingName]
let filePath = NSURL.fileURLWithPathComponents(pathArray)
println(filePath)
// Recording Session
var session = AVAudioSession.sharedInstance()
session.setCategory(AVAudioSessionCategoryPlayAndRecord, error: nil)
audioRecorder = AVAudioRecorder(URL: filePath, settings: nil, error: nil)
audioRecorder.delegate = self
audioRecorder.meteringEnabled = true
audioRecorder.prepareToRecord()
audioRecorder.record()
}
func audioRecorderDidFinishRecording(recorder: AVAudioRecorder!, successfully flag: Bool) {
// ToDo create recorded audio file
if(flag)
{ recordedAudio = RecordedAudio()
recordedAudio.filepathURL = recorder.url
recordedAudio.title = recorder.url.lastPathComponent
// ToDo Perform segue
self.performSegueWithIdentifier("stopRecording", sender: recordedAudio)
}
else {
println("Recording was unsuccessfull")
stopButton.hidden = true
recordButton.enabled = true
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue == "stopRecording") {
let PlaySoundsVC:PlaySoundsViewController = segue.destinationViewController as! PlaySoundsViewController
let data = sender as! RecordedAudio
PlaySoundsVC.receivedAudio = data
}
}
#IBAction func stopAudio(sender: UIButton) {
// Hides recording
recording.hidden = true
audioRecorder.stop()
var audioSession = AVAudioSession.sharedInstance()
audioSession.setActive(false, error: nil)
}
}
My Model class is ,
import Foundation
class RecordedAudio : NSObject{
var filepathURL :NSURL!
var title : String!
}
Here is how My second viewcontroller catch the data and uses it,
class PlaySoundsViewController: UIViewController {
var audioPlayer: AVAudioPlayer!
var receivedAudio: RecordedAudio!
func rateplay (rtt : Float32) {
audioPlayer.stop()
audioPlayer.rate = rtt
audioPlayer.currentTime = 0.0
audioPlayer.play()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// if var filePath = NSBundle.mainBundle().pathForResource("movie_quote", ofType: "mp3")
// {
// // if path is there for mp3
// let filepathurl = NSURL.fileURLWithPath(filePath)
//
// // println(receivedAudio.title)
//
// }
// else {
// println("Path is empty")
//
// }
audioPlayer = AVAudioPlayer(contentsOfURL: receivedAudio.filepathURL, error: nil)
audioPlayer.enableRate = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func playSlow(sender: UIButton) {
// play sloooowllyyyyy
audioPlayer.stop()
audioPlayer.rate = 0.5
audioPlayer.currentTime = 0.0
audioPlayer.play()
}
#IBAction func playFast(sender: UIButton) {
rateplay(1.5)
}
#IBAction func stopAudio(sender: UIButton) {
audioPlayer.stop()
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
Untill I add the below code,
audioPlayer = AVAudioPlayer(contentsOfURL: receivedAudio.filepathURL, error: nil)
audioPlayer.enableRate = true
I was able to move to the second scene, which means, the audio is successfully recorded. But as soon as i access the data like "receivedAudio.filepathURL" I am getting the error,
fatal error: unexpectedly found nil while unwrapping an Optional value
In the prepareForSegue function of the RecordSoundsViewController you need to write segue.identifier == "stopRecording" as the condition.
Currently you have segue == "stopRecording".
Happy Coding!

Resources