UIButton.addTarget with swift 3 and local function - ios

I'm trying to generate multiple buttons programmatically. For the moment, here is what I've got :
for i in POIArray {
let newI = i.replacingOccurrences(of: "GPS30", with: "")
let button = UIButton(type: .custom)
button.setImage(UIImage(named: newI), for: .normal)
button.frame.size.height = 30
button.frame.size.width = 30
button.addTarget(self, action: #selector(buttonAction(texte:newI)), for: UIControlEvents.touchUpInside)
XIBMenu?.stackMenuIco.addArrangedSubview(button)
}
and my function :
func buttonAction(texte: String) {
print("Okay with \(texte)")
}
When I remove the parameter 'texte', it's working. The buttons are well added to the stack but I need that parameter to pass a variable. I get a buildtime error :
Argument of '#selector' does not refer to an '#objc' method, property, or initializer
Yes thank you XCode I know that it's not an objc method, because I'm coding in swift!
Anyone knows a way around ?

Here you need to use objc_setAssociatedObject
Add extension in project:-
extension UIButton {
private struct AssociatedKeys {
static var DescriptiveName = "KeyValue"
}
#IBInspectable var descriptiveName: String? {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.DescriptiveName) as? String
}
set {
if let newValue = newValue {
objc_setAssociatedObject(
self,
&AssociatedKeys.DescriptiveName,
newValue as NSString?,
objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN
)
}
}
}
}
How to use :-
//let newI = i.replacingOccurrences(of: "GPS30", with: "")
button?.descriptiveName = "stringData" //newI use here
How to get parameter :-
func buttonAction(sender: UIButton!) {
print(sender.descriptiveName)
}

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 delete cached SDWebImage from a button's imageView

So I have a UIButton whose imageView is set to an image using a download URL. For this purpose I use SDWebImage.
Problem is, when I press the delete button, I want the image to completely disappear but I guess it does not work because the image is still being retrieved from cache. How do I solve this?
class ViewController: UIViewController{
var profileButton: UIButton = {
let button = UIButton(type: .system)
return button
}()
var user: User?
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(profileButton)
profileButton.addTarget(self, action: #selector(handleDelete), for: .touchUpInside)
self.loadUserPhotos()
}
fileprivate func loadUserPhotos(){
let profileImageUrl = self.user?.profileImageUrl1
if let imageUrl = profileImageUrl, let url = URL(string: imageUrl){
SDWebImageManager.shared().loadImage(with: url, options: .continueInBackground, progress: nil) { (image, _, _, _, _, _) in
self.profileButton.setImage(image?.withRenderingMode(.alwaysOriginal), for: .normal)
}
}
}
#objc func handleDelete(){
self.user?.profileImageUrl1 = ""
self.profileButton.imageView?.image = nil
self.loadUserPhotos()
}
}
To remove the image from UIButton you need to mention the state as well.
self.profileButton.setImage(nil, for: .normal)
You can use :
SDImageCache.shared.removeImage(forKey: url?.description, withCompletion: nil)

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

How to capture UIEvents of UIButton in custom framework?

I create my own framework and in that I need to provide a button and clicking on button I will do something predefined in framework.
Here is my code.
public class WebButton: NSObject {
public func enableWebButton(enable:Bool)
{
if(enable)
{
let appWindow = UIApplication.sharedApplication().keyWindow
let webBtn = UIButton(frame:CGRectMake((appWindow?.frame.size.width)! - 70,(appWindow?.frame.size.height)! - 70,50,50))
let frameworkBundle = NSBundle(forClass: self.classForCoder)
let img = UIImage(named: "btn_icon.png", inBundle: frameworkBundle, compatibleWithTraitCollection: nil)
webBtn.setImage(img, forState: .Normal)
webBtn.layer.cornerRadius = webBtn.frame.size.width / 2;
webBtn.layer.masksToBounds = true
webBtn.addTarget(self, action: "webButtonClick", forControlEvents: .TouchUpInside)
appWindow?.addSubview(webBtn)
print("btn created")
}
}
func webButtonClick()
{
print("btn Clicked")
}
}
In this code, I am getting button on my sample project, but clicking on button, nothing happens.
"btn created"
log is going to print, but
"btn Clicked"
is never going to print.
Please help me to solve this issue.
Thanks in advance.
Try using this code for creating your button and your webButtonClick function:
public func enableWebButton(enable:Bool)
{
if(enable)
{
let appWindow = UIApplication.sharedApplication().keyWindow
let webBtn = UIButton(frame:CGRectMake((appWindow?.frame.size.width)! - 70,(appWindow?.frame.size.height)! - 70,50,50))
let frameworkBundle = NSBundle(forClass: self.classForCoder)
let img = UIImage(named: "btn_icon.png", inBundle: frameworkBundle, compatibleWithTraitCollection: nil)
webBtn.setImage(img, forState: .Normal)
webBtn.layer.cornerRadius = webBtn.frame.size.width / 2;
webBtn.layer.masksToBounds = true
webBtn.addTarget(self, action: "webButtonClick:", forControlEvents: UIControlEvents.TouchUpInside)
appWindow?.addSubview(webBtn)
print("btn created")
}
}
func webButtonClick(sender:UIButton!)
{
print("btn Clicked")
}

Resources