I am new to Swift and Xcode, but I have been reading on here and watching videos on YouTube to guide me along with starting my app. I can't seem to get my button to save its state once the app is closed and re-opened. I used UserDefault To Save Button State as an example, but following it still did not get the button state saved.
I set the state with the Interface Builder and so far have the below code:
#IBAction func ownedButton(_ sender UIButton) {
sender.isSelected = !sender.isSelected
UserDefaults.standard.set(sender.isSelected, forKey: "isSaved")
UserDefaults.standard.synchronize()
}
Clicking the button will keep it selected until clicked again, so it is partially working. It looks like I need some code to in the viewDidLoad section, but I haven't been able to figure out what it should be.
Thank you for any help!
A habit from my Objective-C days is to write a wrapper around the properties in UserDefaults. This way, everything is compile-time checked and the use of strings as keys is minimized:
// Properties.swift
import Foundation
fileprivate var standardDefaults = UserDefaults.standard
class Properties {
static func registerDefaults() {
standardDefaults.register(defaults: [
kIsButton1Selected: false,
kIsButton2Selected: true
])
}
fileprivate static let kIsButton1Selected = "isButton1Selected"
static var isButton1Selected: Bool {
get { return standardDefaults.value(forKey: kIsButton1Selected) as! Bool }
set { standardDefaults.set(newValue, forKey: kIsButton1Selected) }
}
fileprivate static let kIsButton2Selected = "isButton2Selected"
static var isButton2Selected: Bool {
get { return standardDefaults.value(forKey: kIsButton2Selected) as! Bool }
set { standardDefaults.set(newValue, forKey: kIsButton2Selected) }
}
}
Then in your View Controller:
override func viewDidLoad() {
// Always call registerDefaults before you use UserDefaults
// for the first time in your app
Properties.registerDefaults()
button1.isSelected = Properties.isButton1Selected
button2.isSelected = Properties.isButton2Selected
}
#IBAction func ownedButton(_ sender: UIButton) {
sender.isSelected = !sender.isSelected
switch sender {
case button1:
Properties.isButton1Selected = sender.isSelected
case button2:
Properties.isButton2Selected = sender.isSelected
default:
break
}
}
You can replace the switch with key-value observing but remember to remove the KVO on deinit.
Related
I'm using UIPresentationController to prevent the user from accidentally closing a UIViewController presented modally if the user has made any changes. Everything works as it should when it comes to UITextFields since I detect the changes with .editingChanged. A sample code is shown below. I have an UIImageView where the user can change to provide a profile photo. I can enable the save button (rightBarButtonItem) once the user has uploaded an image using didFinishPickingMediaWithInfo in UIImagePickerController but that would not prevent the UIViewController from closing accidentally. Ideally, I would like to change the value of the hasChanges var but it is a get-only property.
var hasChanges: Bool {
guard let customer = customer else { return false }
if
firstNameTextField.text!.isNotEmpty && firstNameTextField.text != customer.firstName
// additional textfields etc…
{
return true
}
return false
}
override func viewWillLayoutSubviews() {
// If our model has unsaved changes, prevent pull to dismiss and enable the save button
let hasChanges = self.hasChanges
isModalInPresentation = hasChanges
saveButton.isEnabled = hasChanges
}
#objc func cancel(_ sender: Any) {
if hasChanges {
// The user tapped Cancel with unsaved changes
// Confirm that they really mean to cancel
confirmCancel(showingSave: false)
} else {
// No unsaved changes, so dismiss immediately
sendDidCancel()
}
}
#objc func textFieldDidChange(_ textField: UITextField) {
if hasChanges {
navigationItem.rightBarButtonItem?.isEnabled = true
} else {
navigationItem.rightBarButtonItem?.isEnabled = false
}
}
func setupTextFieldDelegates() {
let textFields = [all the textfields are included here]
for textField in textFields {
textField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
}
}
To achieve this, you have to create a custom UIImageView, where you have to create a delegate variable and a protocol with a method and need to override the image variable.
when override the image variable you have to call the delegation method inside the didSet method of variable.
class CustomImageView: UIImageView{
override var image: UIImage?{
didSet{
delegate?.didChangeImage()
}
}
var delegate: ImageViewDelegate?
}
protocol ImageViewDelegate {
func didChangeImage()
}
Next in your ViewController set the delegate.
class ImageViewController: UIViewController {
#IBOutlet weak var imageView: CustomimageView!
override func viewDidLoad() {
super.viewDidLoad()
imageView.delegate = self
}
}
If you use the outlet from the storyboard, make sure to provide the custom class name to outlet. Otherwise it will not work.
I have users complaining that there is no sound in my application. I found that this is usually because the silent switch is turned on, and with the handy reply tool I can usually tell them this... however this seem to be an ongoing issue and I need to fix this. I found a way to disable the silent switch with the following code.
import UIKit
import AVKit
import AVFoundation
class InitialViewController: UIViewController {
let defaults = UserDefaults.standard
override func viewDidLoad() {
super.viewDidLoad()
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
//print("AVAudioSession Category Playback OK")
do {
try AVAudioSession.sharedInstance().setActive(true)
//print("AVAudioSession is Active")
} catch _ as NSError {
//print(error.localizedDescription)
}
} catch _ as NSError {
//print(error.localizedDescription)
}
}
}
Fortunately, this code works, but I want users to still have control over whether this code is enabled or not. By default, I want it enabled and let the users toggle this feature on or off. For my other view controller I have this:
import UIKit
import AVFoundation
class CustomTableView: UITableViewController {
let defaults = UserDefaults.standard
let OverrideSwitch2 = "OverrideSwitch2"
#IBOutlet weak var OverrideSwitch: UISwitch!
override func viewDidLoad() {
super.viewDidLoad()
if let fixFin = defaults.value(forKey: OverrideSwitch2) {
OverrideSwitch.isOn = fixFin as! Bool
}
}
#IBAction func `switch`(_ sender: UISwitch) {
defaults.set(sender.isOn, forKey: "OverrideSwitch2")
if (sender.isOn == true){
//Code to switch feature on
} else {
//Code to switch feature off
}
}
}
I have set up an NSUserDefault on the switch so the project remembers the preference, and since it's a simple boolean I could just paste the code in both switch states with one being 'false.' However, this switch is on another view controller and I need my code to be on the initial view controller, otherwise the code doesn't work until the user goes into preferences to change it. I need some help on setting up a switch that can toggle this code on/off and for the UserDefault to remember it.
So I can't believe I figured it out, but I just created another UserDefault that would set a boolean. If true, the code for the AVAudioSession gets run. If false, nothing gets executed. This UserDefault gets toggled through the switch, which also has a UserDefault to remember the switch state.
The Code is as follows:
import UIKit
import CoreGraphics
import AVFoundation
class CustomTableView: UITableViewController {
//This is for the NSUserDefaults
let defaults = UserDefaults.standard
let OverrideSwitch2 = "OverrideSwitch2"
//This Outlet connects the Switch
#IBOutlet weak var OverrideSwitch: UISwitch!
override func viewDidLoad() {
super.viewDidLoad()
//These if statements only run if one is true or false
//True: AVAudioSession is initialized; Silent Switch is Overridden
if UserDefaults.standard.bool(forKey: "Key") == true {
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
//print("AVAudioSession Category Playback OK")
do {
try AVAudioSession.sharedInstance().setActive(true)
print("AVAudioSession")
} catch _ as NSError {
//print(error.localizedDescription)
}
} catch _ as NSError {
//print(error.localizedDescription)
}
}
//False: No Code is executed, silent switch works as normal
if UserDefaults.standard.bool(forKey: "Key") == false {
//No Code Here
}
//This NSUserDefault remembers if the switch is on or off
if let switchState = defaults.value(forKey: OverrideSwitch2) {
OverrideSwitch.isOn = switchState as! Bool
}
}
//This IBAction is connected to the switch; It toggles the UserDefault which your app will remember next time you run it
#IBAction func switchIsChanged(_ OverrideSwitch: UISwitch) {
defaults.set(OverrideSwitch.isOn, forKey: "OverrideSwitch2")
if OverrideSwitch.isOn == true {
UserDefaults.standard.set(true, forKey: "Key")
} else {
UserDefaults.standard.set(false, forKey: "Key")
}
}
}
Simply put, if you toggle the switch 'On' the NSUserDefault runs a function to initialize the session which will only run if the bool is 'true.' If it's false then nothing is run. All I need now is alert telling the user that enabling or disabling the override will only work once they restart the app.
I'm trying to save a bool value to UserDefaults from a UISwitch, and retrieve it in another view. However, I've tried following multiple tutorials and stack answers and none seem to work.
This is how I'm saving it:
class SettingsViewController: UITableViewController {
#IBOutlet weak var soundSwitchOutlet: UISwitch!
#IBAction func soundSwitch(_ sender: UISwitch) {
UserDefaults.standard.set(soundSwitchOutlet.isOn, forKey: "sound")
}
and this is how I'm trying to retrieve it in another view:
if let savedValue = UserDefaults.standard.bool(forKey: "sound") {
boolValue = savedValue
}
//this is inside viewDidLoad and "boolValue" was declared outside viewDidLoad//
For a reason this code is giving me errors and none of the things I've tried have worked. How can I save a bool to UserDefaults and retrieve it in another view?
Edit: I think I fixed the first part. However, the way I'm retrieving the boolean seems to be totally wrong. Also: No other stackExchange answer responds to what I'm asking, at least not in swift.
As Leo mentioned in the comments bool(forKey returns a non-optional Bool. If the key does not exist false is returned.
So it's simply
boolValue = UserDefaults.standard.bool(forKey: "sound")
Calling synchronize() as suggested in other answers is not needed. The framework updates the user defaults database periodically.
Do it like this.
In your first view controller.
create an IBoutlet connection to your UISwitch
And then the action for your UISwitch. so in the end, your first view controller should look like this.
import UIKit
class FirstViewController: UIViewController {
#IBOutlet weak var myswitch: UISwitch! // Outlet connection to your UISwitch (just control+ drag it to your controller)
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func myswitchAction(_ sender: Any) { // Action for your UISwitch
var myswitctBool : Bool = false // create a local variable that holds your bool value. assume that in the beginning your switch is offed and the boolean value is `false`
if myswitch.isOn == true { // when user turn it on then set the value to `true`
myswitctBool = true
}
else { // else set the value to false
myswitctBool = false
}
// finally set the value to user default like this
UserDefaults.standard.set(myswitctBool, forKey: "mySwitch")
//UserDefaults.standard.synchronize() - this is not necessary with iOS 8 and later.
}
}
End of the first view controller
Now in your second view controller
you can get the value of userdefault, which you set in first view controller. I put it in the viewdidload method to show you how it works.
import UIKit
class SecondViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let myswitchBoolValuefromFirstVc : Bool = UserDefaults.standard.bool(forKey: "mySwitch")// this is how you retrieve the bool value
// to see the value, just print those with conditions. you can use those for your things.
if myswitchBoolValuefromFirstVc == true {
print("true")
}
else {
print("false")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
Hope this will help to you. good luck
Use this line of code:
#IBAction func soundSwitch(_ sender: UISwitch) {
UserDefaults.standard.set(soundSwitchOutlet.isOn, forKey: "sound")
}
insteadof :
#IBAction func soundSwitch(_ sender: UISwitch) {
UserDefaults.standard.set(soundSwitchOutlet, forKey: "sound")
}
Try this:
#IBAction func soundSwitchs(_ sender: Any)
{
UserDefaults.standard.set(soundSwitchOutlet.isOn, forKey: "sound")
UserDefaults.standard.synchronize()
}
//this is inside viewDidLoad and "boolValue" was declared outside viewDidLoad//
boolValue = UserDefaults.standard.bool(forKey: "sound")
I've been debugging my code and found that my manager was deinitialised (that was cause of my bug - not calling delegate methods).
What's strange, that during debugging process I've used "po" command after setting the manager's delegate (weak) and it prevented it from being deinitialised (delegate methods were called).
Why is that? Is it proper behaviour?
Xcode 8.3, swift 3.1
EDIT:
//a tap starts everything :)
#IBAction func shareButtonPressed(_ sender: Any) {
let requestManager = FacebookPostRouteRequest() //bug fixed by changing to instance variable
requestManager.delegate = self
requestManager.showShareBadgeDialog(self.badge!, onViewController: self)
}
//in FacebookPostRouteRequest
final weak var delegate: FacebookPostRouteRequestDelegate?
func showShareBadgeDialog(_ badge: Badge, onViewController viewController: UIViewController) {
let dialog = self.initDialog(onViewController: viewController)
guard let imageURL = badge.imageURL else {
self.delegate?.facebookPostRouteRequest(self, didCompleteWithResult: false)
return
}
dialog.shareContent = self.generateImageShareContent(imageURL)
self.show(dialog)
}
private func show(_ dialog: FBSDKShareDialog) {
OperationQueue.main.addOperation {
dialog.delegate = self //when printed out dialog.delegate delegate methods were called! Deinit of FacebookPostRouteRequest is not called.
let showResult = dialog.show()
...
}
}
extension FacebookPostRouteRequest: FBSDKSharingDelegate {
func sharer(_ sharer: FBSDKSharing!, didCompleteWithResults results: [AnyHashable : Any]!) {
...
}
//other delegate methods implemented as well
}
Your problem is here:
#IBAction func shareButtonPressed(_ sender: Any) {
let requestManager = FacebookPostRouteRequest()
requestManager.delegate = self
requestManager.showShareBadgeDialog(self.badge!, onViewController: self)
}
After the last line, the requestManager object will be disposed because it's no longer referenced and will not call any of the delegate methods.
Make requestManager an instance variable:
let requestManager = FacebookPostRouteRequest()
#IBAction func shareButtonPressed(_ sender: Any) {
requestManager.delegate = self
requestManager.showShareBadgeDialog(self.badge!, onViewController: self)
}
Your issues with the debugger are probably race conditions for stopping the main thread.
I am new to OS X app development. I manage to built the NSComboBox (Selectable, not editable), I can get it indexOfSelectedItem on action button click, working fine.
How to detect the the value on change? When user change their selection, what kind of function I shall use to detect the new selected index?
I tried to use the NSNotification but it didn't pass the new change value, always is the default value when load. It is because I place the postNotificationName in wrong place or there are other method should use to get the value on change?
I tried searching the net, video, tutorial but mostly written for Objective-C. I can't find any answer for this in SWIFT.
import Cocoa
class NewProjectSetup: NSViewController {
let comboxRouterValue: [String] = ["No","Yes"]
#IBOutlet weak var projNewRouter: NSComboBox!
#IBAction func btnAddNewProject(sender: AnyObject) {
let comBoxID = projNewRouter.indexOfSelectedItem
print(“Combo Box ID is: \(comBoxID)”)
}
#IBAction func btnCancel(sender: AnyObject) {
self.dismissViewController(self)
}
override func viewDidLoad() {
super.viewDidLoad()
addComboxValue(comboxRouterValue,myObj:projNewRouter)
self.projNewRouter.selectItemAtIndex(0)
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(
self,
selector: “testNotication:”,
name:"NotificationIdentifier",
object: nil)
NSNotificationCenter.defaultCenter().postNotificationName("NotificationIdentifier", object: projNewRouter.indexOfSelectedItem)
}
func testNotication(notification: NSNotification){
print("Found Combo ID \(notification.object)")
}
func addComboxValue(myVal:[String],myObj:AnyObject){
let myValno: Int = myVal.count
for var i = 0; i < myValno; ++i{
myObj.addItemWithObjectValue(myVal[i])
}
}
}
You need to define a delegate for the combobox that implements the NSComboBoxDelegate protocol, and then use the comboBoxSelectionDidChange(_:) method.
The easiest method is for your NewProjectSetup class to implement the delegate, as in:
class NewProjectSetup: NSViewController, NSComboBoxDelegate { ... etc
Then in viewDidLoad, also include:
self.projNewRouter.delegate = self
// self (ie. NewProjectSetup) implements NSComboBoxDelegate
And then you can pick up the change in:
func comboBoxSelectionDidChange(notification: NSNotification) {
print("Woohoo, it changed")
}