I used to many attempts to solve this error but i can't.
i have [Int: Int] dictionary
want's to add this dictionary to default for use latter
Sample Code Of mine
var ListIds = [Int: Int]()
ListIds[1] = 1
defaults.set(["data": ListIds], forKey: "cartKeys")
Some quick web searching and I found this: Hacking with Swift forums - Save [Int:Int] to UserDefaults
I edited the code slightly to make it an extension, and to allow passing the Key to use in UserDefaults:
extension UserDefaults {
func saveIntDictionary(key: String, intDictionary: [Int:Int]) {
let encoder = PropertyListEncoder()
guard let data = try? encoder.encode(intDictionary) else {
return
}
set(data, forKey: key)
}
func retrieveSavedIntDictionary(key: String) -> [Int:Int]? {
let decoder = PropertyListDecoder()
guard let data = data(forKey: key),
let intDictionary = try? decoder.decode([Int:Int].self, from: data) else {
return nil
}
return intDictionary
}
}
and it can be used like this:
// note: it returns an optional -- it will be nil if the key does not exist
// handle that appropriately
let sv = UserDefaults.standard.retrieveSavedIntDictionary(key: "cartKeys")
// save to UserDefaults
UserDefaults.standard.saveIntDictionary(key: "cartKeys", intDictionary: listIDs)
Here's a quick runnable example:
class ViewController: UIViewController {
var listIDs: [Int : Int] = [:]
// a scrollable non-editable text view to display the dictionary
let displayTextView = UITextView()
override func viewDidLoad() {
super.viewDidLoad()
if let sv = UserDefaults.standard.retrieveSavedIntDictionary(key: "cartKeys") {
listIDs = sv
print("Loaded from UserDefaults!")
print(String(describing: listIDs))
print()
} else {
print("Nothing in UserDefaults")
print()
}
// let's have two buttons
// Add a new ID
// Reset (clears the dictionary)
let b1 = UIButton()
b1.setTitle("Add a new ID", for: [])
b1.setTitleColor(.white, for: .normal)
b1.setTitleColor(.lightGray, for: .highlighted)
b1.backgroundColor = .systemBlue
b1.layer.cornerRadius = 8
b1.addTarget(self, action: #selector(addEntry(_:)), for: .touchUpInside)
let b2 = UIButton()
b2.setTitle("Reset", for: [])
b2.setTitleColor(.white, for: .normal)
b2.setTitleColor(.lightGray, for: .highlighted)
b2.backgroundColor = .systemRed
b2.layer.cornerRadius = 8
b2.addTarget(self, action: #selector(reset(_:)), for: .touchUpInside)
displayTextView.backgroundColor = .yellow
displayTextView.font = .monospacedSystemFont(ofSize: 16.0, weight: .regular)
displayTextView.isEditable = false
// a stack view for the buttons and display text view
let stack = UIStackView(arrangedSubviews: [b1, displayTextView, b2])
stack.axis = .vertical
stack.spacing = 12
stack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stack)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
stack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
stack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
stack.centerYAnchor.constraint(equalTo: g.centerYAnchor, constant: -20.0),
displayTextView.heightAnchor.constraint(equalToConstant: 240.0),
])
updateDisplay()
}
func updateDisplay() {
var s: String = ""
if listIDs.isEmpty {
s = "No entries in listIDs"
} else {
listIDs.forEach { item in
s += "Key: \(item.key)\t\tValue: \(item.value)\n"
}
}
displayTextView.text = s
}
#objc func addEntry(_ sender: Any?) {
// add a new entry
let n: Int = listIDs.count + 1
let v: Int = Int.random(in: 1...20000)
listIDs[n] = v
// save to UserDefaults
UserDefaults.standard.saveIntDictionary(key: "cartKeys", intDictionary: listIDs)
updateDisplay()
}
#objc func reset(_ sender: Any?) {
// clear the dictionary
listIDs = [:]
// save to UserDefaults
UserDefaults.standard.removeObject(forKey: "cartKeys")
//UserDefaults.standard.saveIntDictionary(key: "cartKeysA", intDictionary: listIDs)
updateDisplay()
}
}
When run, it will look like this. Each tap of "Add a new ID" will add a new sequential entry to the dictionary (with a random value) and save to UserDefaults. Quit the app and re-run it to see the dictionary loaded. Tap "Reset" to clear it:
A couple notes:
1 - Learn about naming conventions... listIDs instead of ListIds for example.
2 - Saving "data" this way is generally not a good idea -- UserDefaults is much better suited to storing things like "app settings" for example. If your dictionary of "IDs" may grow large, you probably want to look at other data persistence methods.
Related
This question already exists:
I am making a HangMan game and I'm having problems with letters being doubled each time it loops [closed]
Closed last month.
So I've been creating a hangman game and been having problems with each time a user guesses a correct letter again, the previous correct letter/letters gets removed.??
I tried a lot of if elses to hold each index of the letter and append if its correct but that did not work. So in general let's say a user got their first letter correct which was A. This is what happens..
XXXAXXAX
But the Second letter they get correct, lets say B this happens...
XBXXXXXX
they are supposed to bring A along to the next iteration and so onn.
can anyone see what's the problem? Here's my code below..
class ViewController: UIViewController {
var allWords = [String]()
var usedLetters = [String]()
var startWords = [String]()
var promptWord = String()
var randomWord = ""
override func viewDidLoad() {
super.viewDidLoad()
EnterGuess()
fileWork()
print(randomWord)
title = "GUESS A Letter: ?????????)"
}
func fileWork() {
if let startWordsURL = Bundle.main.url(forResource: "start", withExtension: "txt") {
if let startWords = try? String(contentsOf: startWordsURL) {
allWords = startWords.components(separatedBy: "\n")
let wordssss = startWords.randomElement()
randomWord = allWords.randomElement()!
}
}
}
func EnterGuess() {
let ac = UIAlertController(title: "Guess a letter", message: nil, preferredStyle: .alert)
ac.addTextField()
var submitGuess = UIAlertAction(title: "Submit", style: .default) {_ in
guard var answer = ac.textFields?[0].text else {return }
answer
self.submit(answer)
self.EnterGuess()
}
ac.addAction(submitGuess)
present(ac, animated: true)
}
func submit(_ answer: String) {
let guessedLetter = answer
let wordArray = randomWord.map(String.init)
var hidden = Array(repeating: "x", count: randomWord.count)
for index in hidden.indices {
if wordArray[index] == answer {
hidden[index] = wordArray[index]
}
}
print(hidden.joined())
// print(wordArray)
title = String(describing: hidden.joined())
}
}
The problem is that you are "re-setting" the hidden string with each letter guess.
Instead, you want to create a class-level var:
var hidden = [String]()
and "re-set" it to "xxxxxx..." when you start a new game.
Then, with each guess, you'll be keeping the old letters and replacing new letters.
Suppose the game word is EASY:
hidden is "xxxx"
guessed letter is "A"
hidden goes from "xxxx" to "xAxx"
guessed letter is "Y"
hidden goes from "xAxx" to "xAxY"
guessed letter is "E"
hidden goes from "xAxY" to "EAxY"
and so on.
Here's a quick example you can run:
class HangmanViewController: UIViewController, UITextFieldDelegate {
var allWords: [String] = [
"easy",
"birthday",
"laboratory",
"hangman",
"iphone",
"requirements",
"gaming",
]
var usedLetters = [String]()
var startWords = [String]()
var promptWord = String()
var randomWord = ""
var hidden = [String]()
let tf = UITextField()
let progressLabel = UILabel()
let usedLabel = UILabel()
let newGameButton = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
tf.textAlignment = .center
tf.borderStyle = .roundedRect
tf.textAlignment = .center
tf.font = .monospacedSystemFont(ofSize: 24.0, weight: .light)
tf.delegate = self
progressLabel.textAlignment = .center
progressLabel.font = .monospacedSystemFont(ofSize: 24.0, weight: .light)
progressLabel.backgroundColor = .yellow
usedLabel.textAlignment = .center
usedLabel.font = .monospacedSystemFont(ofSize: 16.0, weight: .light)
usedLabel.backgroundColor = .cyan
newGameButton.setTitle("New Game", for: [])
newGameButton.setTitleColor(.white, for: .normal)
newGameButton.setTitleColor(.lightGray, for: .highlighted)
newGameButton.backgroundColor = .systemBlue
newGameButton.layer.cornerRadius = 8
newGameButton.layer.borderColor = UIColor.blue.cgColor
newGameButton.layer.borderWidth = 1
[tf, progressLabel, usedLabel, newGameButton].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
}
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
tf.widthAnchor.constraint(equalToConstant: 80.0),
tf.topAnchor.constraint(equalTo: g.topAnchor, constant: 80.0),
tf.centerXAnchor.constraint(equalTo: g.centerXAnchor),
progressLabel.widthAnchor.constraint(equalToConstant: 300.0),
progressLabel.topAnchor.constraint(equalTo: tf.bottomAnchor, constant: 40.0),
progressLabel.centerXAnchor.constraint(equalTo: g.centerXAnchor),
usedLabel.widthAnchor.constraint(equalToConstant: 300.0),
usedLabel.topAnchor.constraint(equalTo: progressLabel.bottomAnchor, constant: 40.0),
usedLabel.centerXAnchor.constraint(equalTo: g.centerXAnchor),
newGameButton.widthAnchor.constraint(equalToConstant: 200.0),
newGameButton.topAnchor.constraint(equalTo: usedLabel.bottomAnchor, constant: 40.0),
newGameButton.centerXAnchor.constraint(equalTo: g.centerXAnchor),
])
newGameButton.addTarget(self, action: #selector(newGameTapped(_:)), for: .touchUpInside)
newGame()
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// upper-case the entered letter
let s = string.uppercased()
// we don't want to process if a string was pasted into the field
if s.count != 1 {
return false
}
// only allow A - Z
if s.rangeOfCharacter(from: .uppercaseLetters) == nil {
return false
}
// replace the current text
textField.text = s
// process the entered letter
submit(s)
// don't let the textfield process the string
return false
}
func newGame() {
// cycle the array of game words
allWords.append(allWords.removeFirst())
// safely unwrap
guard let w = allWords.first else {
fatalError("Bad setup")
}
// upper-case the word
randomWord = w.uppercased()
// set hidden string to "####..."
hidden = Array(repeating: "#", count: randomWord.count)
// clear used letters
usedLetters = []
// update the game progress label
progressLabel.text = String(hidden.joined())
progressLabel.textColor = .black
// update used letters label
usedLabel.text = " "
// hide the new game button
newGameButton.isHidden = true
// re-enable text field
tf.isUserInteractionEnabled = true
// for development
print("New Game Word is:", randomWord)
}
func submit(_ answer: String) {
if usedLetters.contains(answer) {
return
}
usedLetters.append(answer)
usedLabel.text = usedLetters.joined()
let wordArray = randomWord.map(String.init)
for index in hidden.indices {
if wordArray[index] == answer {
hidden[index] = wordArray[index]
}
}
progressLabel.text = hidden.joined()
if hidden == wordArray {
progressLabel.textColor = .red
// clear and disable text field
tf.text = ""
tf.isUserInteractionEnabled = false
// dismiss keyboard
view.endEditing(true)
// show the new game button
newGameButton.isHidden = false
}
}
#objc func newGameTapped(_ sender: UIButton) {
newGame()
}
}
It will look about like this (the cyan label will show the used letters). I'll type T, R, Y, S, M, E, A:
I want to add dynamic number of buttons to my VC. So i am looping through my buttons array model and instantiating UIButtons. The problem is with adding target to these buttons. I want to pass in a string to the selector when adding a target, however Xcode compiler doesn't let me do that
Argument of '#selector' does not refer to an '#objc' method, property, or initializer
#objc func didTapOnButton(url: String) { }
let button = UIButton()
button.addTarget(self, action: #selector(didTapOnButton(url: "Random string which is different for every bbutton ")), for: .touchUpInside)
Is there any other solution other than using a custom UIButton
I don't think it is possible to do what you are attempting, you can try like this:
var buttons: [UIButton: String] = []
let button = UIButton()
let urlString = "Random string which is different for every button"
buttons[button] = urlString
button.addTarget(self, action: #selector(didTapOnButton), for: .touchUpInside
#objc func didTapOnButton(sender: UIButton) {
let urlString = self.buttons[sender]
// Do something with my URL
}
As I remember UIButton is hashable...
Another option would be to extend UIButton to hold the information you want:
extension UIButton {
private static var _urlStringComputedProperty = [String: String]()
var urlString String {
get {
let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self))
return Self._urlStringComputedProperty[tmpAddress]
}
set(newValue) {
let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self))
Self._urlStringComputedProperty[tmpAddress] = newValue
}
}
}
let button = UIButton()
button.urlString = "Random string which is different for every button"
button.addTarget(self, action: #selector(didTapOnButton), for: .touchUpInside
#objc func didTapOnButton(sender: UIButton) {
let urlString = sender.urlString
// Do something with my URL
}
I have created UIViews programmatically based on the number of items i stored in my UserDefaults and each UIView represents an item from the userDefaults and have added UITapGestureRecognizer on top of it. Now this UIViews when clicked will send my user to a new view controller, now my problem is how do I pass a parameter which will hold a value so that the new view controller can determine which view was clicked. Below is my code
//Retrieving my userDefaults values
let items = preferences.object(forKey: selectedOffer)
//How i loop and create my UIViews
if let array = items as! NSArray?{
totalOffers = array.count
let castTotalOffers = CGFloat(totalOffers)
var topAnchorConstraint: CGFloat = 170
var cardHeight: CGFloat = 145
for obj in array {
if let dict = obj as? NSDictionary{
offerName = dict.value(forKey: "NAME") as! String
let offerPrice = dict.value(forKey: "PRICE") as! String
let offerDescription = dict.value(forKey: "DESCRIPTION") as! String
//creating the uiview
let offerView = UIView()
self.scrollView.addSubview(offerView)
offerView.translatesAutoresizingMaskIntoConstraints = false
offerView.topAnchor.constraint(equalTo: self.appBackImage.bottomAnchor, constant: topAnchorConstraint).isActive = true
offerView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 20.0).isActive = true
offerView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -20.0).isActive = true
offerView.backgroundColor = UIColor.white
offerView.heightAnchor.constraint(equalToConstant: 130).isActive = true
//transforming to cards
offerView.layer.cornerRadius = 2
offerView.layer.shadowOffset = CGSize(width: 0, height: 5)
offerView.layer.shadowColor = UIColor.black.cgColor
offerView.layer.shadowOpacity = 0.1
self.scrollView.contentSize.height = CGFloat(totalOffers) + topAnchorConstraint + 70
//Adding gesture
let touchRec = UITapGestureRecognizer(target: self, action: #selector(goToBuyBundle(offerClicked:offerName)))
offerView.addGestureRecognizer(touchRec)
}
}
}
//Function to go to buy offer
#objc func goToBuyBundle(_sender: UITapGestureRecognizer, offerClicked:String){
guard let moveTo = storyboard?.instantiateViewController(withIdentifier: "BuyOfferViewController") as? BuyOfferViewController else {return}
moveTo.selectedOffer = offerClicked
self.addChildViewController(moveTo)
moveTo.view.frame = self.view.frame
self.view.addSubview(moveTo.view)
moveTo.didMove(toParentViewController: self)
}
Just want a way when i navigate to the next view controller i can retrieve which UIView was clicked by using the offerName.
Thanks in Advance
Make your custom View and store the parameter that you want to pass through the Gesture Recognizer inside the view.
class GestureView: UIView{
var myViewValue: String? // Or whichever type of value you want to store and send via the gesture
}
When you initiate your view, add the value as per your requirement:
let panReceptor = GestureView()
panReceptor.myViewValue = "Hello World"
Add a simple TapGesture on this custom view and you may pass the value as below:
let tapGesture = UITapGestureRecognizer.init(target: self, action: #selector(viewTapped(sender:)))
panReceptor.addGestureRecognizer(tapGesture)
#objc func viewTapped(sender: UITapGestureRecognizer){
guard let unwrappedView = sender.view as? GestureView else { return }
print("Gesture View value : \(unwrappedView.myViewValue)")
}
In the above example I have in effect passed a String parameter through the sender.view.
You may pass any type in this manner and use the value as per your requirement in the selector method.
You could add custom variable to UITapGestureRecognizer something like:
import UIKit
private var assocKey : UInt8 = 0
extension UITapGestureRecognizer {
public var offerName:String{
get{
return objc_getAssociatedObject(self, &assocKey) as! String
}
set(newValue){
objc_setAssociatedObject(self, &assocKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}
}
And then use it like:
...
let touchRec = UITapGestureRecognizer(target: self, action: #selector(goToBuyBundle(offerClicked:offerName)))
touchRec.offerName = offerName
offerView.addGestureRecognizer(touchRec)
...
#objc func goToBuyBundle(_sender: UITapGestureRecognizer, offerClicked:String){
guard let moveTo = storyboard?.instantiateViewController(withIdentifier: "BuyOfferViewController") as? BuyOfferViewController else {return}
moveTo.selectedOffer = sender.offerName
...
}
I have code to sum four text fields and output the total in a label. Currently the code sums the fields after finishing editing, that is, selecting another text field. Is there a way to sum the text fields as the user types?
#IBAction func TankFuelChanged(_ sender: Any) {
let leftMainTankQuantityValue = Int(leftMainTankQuantity.text ?? "") ?? 0
let rightMainTankQuantityValue = Int(rightMainTankQuantity.text ?? "") ?? 0
let auxTankQuantityValue = Int(auxTankQuantity.text ?? "") ?? 0
let tailTankQuantityValue = Int(tailTankQuantity.text ?? "") ?? 0
let total = leftMainTankQuantityValue + rightMainTankQuantityValue + auxTankQuantityValue + tailTankQuantityValue
totalFuelLoad.text = "\(total)"
What you are looking for is an event triggered when text field changes. You can drag an action from storyboard or you can add them programmatically by using addTarget similar to UIButton but need to use event editingChanged. Check the following code:
var allTextFields: [UITextField] {
return [leftMainTankQuantity, rightMainTankQuantity, auxTankQuantity, tailTankQuantity]
}
override func viewDidLoad() {
super.viewDidLoad()
allTextFields.forEach { $0.addTarget(self, action: #selector(onTextFieldChange), for: .editingChanged) }
}
#objc private func onTextFieldChange() {
updateResult()
}
private func updateResult() {
let strings: [String] = allTextFields.compactMap { $0.text } // Will remove all nil texts
let integers: [Int] = strings.compactMap { Int($0) } // Will remove all non-integer texts
let sum = integers.reduce(0, { $0 + $1 }) // Will compute a sum
print(sum) // TODO: update your result here
}
A method must be marked #objc because of the #selector next to that I hope code speaks for itself.
I have made a favorite button for my detailViews( i have a master-detail app ) and it saves the button state generally for all cells/DetailsViews...I want if i press index's 3 cell it will save the button state only for there , if i go to index 4 it will save it individually to that row and won't save the same state to all cells.
Favorite Button:
//create a new button
let Favoritebutton: UIButton = UIButton(type: UIButtonType.custom)
//set image for button
Favoritebutton.setImage(UIImage(named: "EmptyHeart.png"), for: .normal)
Favoritebutton.setImage(UIImage(named: "FilledHeart.png"), for: .selected)
//add function for button
Favoritebutton.addTarget(self, action: #selector(self.button), for: .touchUpInside)
//set frame
Favoritebutton.frame = CGRect(x:0,y: 0,width: 35,height: 35)
Favoritebutton.isSelected = UserDefaults.standard.bool(forKey: "isSaved")
let barButton = UIBarButtonItem(customView: Favoritebutton)
//assign button to navigationbar
self.navigationItem.rightBarButtonItem = barButton
func button(sender: UIButton) {
audioPlayer.play()
let newValue = !sender.isSelected
sender.isSelected = newValue
UserDefaults.standard.set(newValue, forKey: "isSaved")
let tabItem = self.tabBarController?.tabBar.items![3]
sel_val = tabItem?.badgeValue
if(sel_val == nil){
sel_val = "0"
}
let sel_num = Int(sel_val!)
let fav: NSMutableArray = []
fav.add(barImage)
fav.add(barName)
fav.add(streetName)
if sender.isSelected {
tabItem!.badgeValue = String(format: "%d", sel_num! + 1)
favorite.add(fav)
} else {
tabItem!.badgeValue = String(format: "%d", sel_num! - 1)
favorite.remove(fav)
}
}
How can i make the button save state for each indexPath individually like i want?
This will help me finish my favorites feature so your help will be really really appreciated !
Thank you for your help !
What you want is to store array or dictionary, depends on the situation. Here is an example how to store an array How to save NSMutablearray in NSUserDefaults. I suggest to get the values from defaults when app has started and assign it to the value, so you have a local copy. Do not forget to save array back to defaults after every change, or at least when leaving the app.
EDIT:
You want to store Dictionary of bool, so that you can access values for every row. I discourage you to use indexPath, rather you should use an identifier which will be unique for a row.
These will serve as local storage for your app
let favoritesKey = "favorites"
var favorites: [Int: Bool] = [:]
This is how you obtain saved dictionary:
favorites = userDefaults.object(forKey: favoritesKey) as? [Int : Bool] ?? [:]
This is how you change your values:
favorites[index] = true / false
This is how you obtain your values:
let value = favorites[index]
This is how you save values:
let userDefaults = UserDefaults.standard
userDefaults.set(favorites, forKey: favoritesKey)