Save Favorite button state with indexPath.row - ios

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)

Related

How To Add [int:int] element in user Defaults in swift5

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.

Primitive type parameters in Swift Selector function

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
}

How to add a parameter to UITapGestureRecognizer so that the action function can access that parameter

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
...
}

image is not set on UIButton in swift

when i pass static value in bktheme[1] then image is properly set on button.
when i already get value from NSUserDefaults and set as bktheme as under
Defaults = NSUserDefaults.standardUserDefaults()
let shape = Defaults.integerForKey("Chaperone")
print(shape) // output: 1
let str = bkthemes[shape]
keyImageTypeOne = UIImage(named: str) as UIImage?
then image is not set so, how can i set image on button
Please try below code.
If you get image name in str then only below code works.
So i want to suggest you at first print value of str in console.
Defaults = NSUserDefaults.standardUserDefaults()
let shape = Defaults.integerForKey("Chaperone")
print(shape) // output: 1
let str = bkthemes[shape]
yourButton.setImage(UIImage(named: str), forState: UIControlState.Normal)
If you get any issue then give me value of str.
your code is fine just do like
keyImageTypeOne.setImage(UIImage(named: str) as UIImage?, forState: .Normal)
You can do this:
let button:UIButton = UIButton(type: UIButtonType.System)
let image:UIImage = UIImage(named:"nope")!
button.setImage(image, forState: UIControlState.Normal)

How to compare UIImages in arrays?

I'm making an application where it would be nice to be able to compare values inside one array/set.
Let's say I have constants and an array like this:
let blueImage = UIImage(named: "blue")
let redImage = UIImage(named: "red")
button.setImage(blueImage, forState: .Normal)
button2.setImage(redImage, forState: .Normal)
button3.setImage(blueImage, forState: .Normal)
var imageArray:Array<UIImage> = [button.currentImage, button2.currentImage, button3.currentImage]
Is it then possible to check/compare the values in my array and replace the red images with the blue ones.
More specifically is there a way I can check if 2/3 of the images in the array contains a specific image(blueImage), and then replace the last value(redImage) with (blueImage) so that all has the same picture.
I guess you could filter the array with something along these lines:
let filteredArray = filter(imageArray) { $0 == blueImage }
and then run a count.
You could also iterate over your array:
let countBlue = 0
for i in 0..<imageArray.count {
if imageArray[i] == blueImage {
countBlue ++
}
}
To replace an element:
imageArray[2] = blueImage

Resources