pass a UIButton into a function - ios

I have a function that runs when a button is pressed.
Inside that function is another function.
I would like to pass the pressed button into the inner function so that I can change the text of the button depending on stuff in that inner function.
#IBAction func newItem(sender: AnyObject) {
let urlFetch:String = urlField.text!
self.service.createNewItem(urlFetch, I_WANT_BUTTON_HERE)
}
How do I pass the button into the function?
If it helps this is the function that I am passing it to:
func createNewItem(item_url: String, plus_button: UIButton) {
let dataDictionary = ["url" : item_url]
self.post("item/create", data: dataDictionary).responseJSON { (response) -> Void in
plus_button.titleLabel!.text = "success"
}
}
Later I will add an if statement that changes the text depending on what the response is.

The object passed into newItem method is actually the button you tapped so you can securely convert the type of parameter sender into UIButton like below:
#IBAction func newItem(sender: UIButton) {
...
self.service.createNewItem(urlFetch, sender)
}
There is also one thing though. Setting the text of titleLabel is not the right way of updating the title of button. You should be using setTitle:forState instead:
func createNewItem(item_url: String, plus_button: UIButton) {
...
plus_button.setTitle("success", forState: .Normal)
}

Instead of sending AnyObject as parameter in the newItem function, pass a UIButton.
#IBAction func newItem(sender: UIButton) {
let urlFetch:String = urlField.text!
self.service.createNewItem(urlFetch, sender)
}

Related

Using multiple toggles I'would like to regroup all toggles in one action

I'm trying to create an array composed of multiple tools.
The array changes depending on which tools are carried.
Expecting a long list of tools I would like to avoid having to write an action for each single element to add or remove from the array. ( I managed this part as you'll see below)
Instead I'd like to use the name of the toggle switch and apply the same action to all the toggles on the screen.
I came up with this inelegant method:
import UIKit
class mainVC: UIViewController {
var myEquippement: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func shovelSwitch(_ sender: UISwitch) {
if sender.isOn{
myEquippement.append("Shovel")
}
else {
let subjectIndex = myEquippement.firstIndex(of: "Shovel")
myEquippement.remove(at: subjectIndex!)
}
}
#IBAction func hammerSwitch(_ sender: UISwitch) {
if sender.isOn{
myEquippement.append("Hammer")
}
else {
let subjectIndex = myEquippement.firstIndex(of: "Hammer")
myEquippement.remove(at: subjectIndex!)
}
}
#IBAction func screwdriverSwitch(_ sender: UISwitch) {
if sender.isOn{
myEquippement.append("Screwdriver")
}
else {
let subjectIndex = myEquippement.firstIndex(of: "Screwdriver")
myEquippement.remove(at: subjectIndex!)
}
}
#IBAction func sawSwitch(_ sender: UISwitch) {
if sender.isOn{
myEquippement.append("Saw")
}
else {
let subjectIndex = myEquippement.firstIndex(of: "Saw")
myEquippement.remove(at: subjectIndex!)
}
}
}
could you point me please to a better way of doing it.
I thought of using something like this:
#IBAction func toggleSwitched(_ sender: UISwitch) {
if sender.isOn{
myEquippement.append(sender.title!)
}
else {
let subjectIndex = myEquippement.firstIndex(of: sender.title!)
myEquippement.remove(at: subjectIndex!)
}
}
but sender.title always returns a nil value, the force unwrapping crashes the app.
Thanks in advance for your help.
One solution would be to have a master array of tool names. When creating a switch, assign its tag property to the corresponding index within that array.
Then in your single switch handler you can use the sender's tag to get the name of the tool from the master list of tool names. That will give you the title for the switch. Then your logic for adding/removing the title from myEquipment can be used.

Passing arguments to selector in Swift

I'm programmatically adding a UITapGestureRecognizer to one of my views:
let gesture = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(modelObj:myModelObj)))
self.imageView.addGestureRecognizer(gesture)
func handleTap(modelObj: Model) {
// Doing stuff with model object here
}
The first problem I encountered was "Argument of '#selector' does not refer to an '#Objc' method, property, or initializer.
Cool, so I added #objc to the handleTap signature:
#objc func handleTap(modelObj: Model) {
// Doing stuff with model object here
}
Now I'm getting the error "Method cannot be marked #objc because the type of the parameter cannot be represented in Objective-C.
It's just an image of the map of a building, with some pin images indicating the location of points of interest. When the user taps one of these pins I'd like to know which point of interest they tapped, and I have a model object which describes these points of interest. I use this model object to give the pin image it's coordinates on the map so I thought it would have been easy for me to just send the object to the gesture handler.
It looks like you're misunderstanding a couple of things.
When using target/action, the function signature has to have a certain form…
func doSomething()
or
func doSomething(sender: Any)
or
func doSomething(sender: Any, forEvent event: UIEvent)
where…
The sender parameter is the control object sending the action message.
In your case, the sender is the UITapGestureRecognizer
Also, #selector() should contain the func signature, and does NOT include passed parameters. So for…
func handleTap(sender: UIGestureRecognizer) {
}
you should have…
let gesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(sender:)))
Assuming the func and the gesture are within a view controller, of which modelObj is a property / ivar, there's no need to pass it with the gesture recogniser, you can just refer to it in handleTap
Step 1: create the custom object of the sender.
step 2: add properties you want to change in that a custom object of the sender
step 3: typecast the sender in receiving function to a custom object and access those properties
For eg:
on click of the button if you want to send the string or any custom object then
step 1: create
class CustomButton : UIButton {
var name : String = ""
var customObject : Any? = nil
var customObject2 : Any? = nil
convenience init(name: String, object: Any) {
self.init()
self.name = name
self.customObject = object
}
}
step 2-a: set the custom class in the storyboard as well
step 2-b: Create IBOutlet of that button with a custom class as follows
#IBOutlet weak var btnFullRemote: CustomButton!
step 3: add properties you want to change in that a custom object of the sender
btnFullRemote.name = "Nik"
btnFullRemote.customObject = customObject
btnFullRemote.customObject2 = customObject2
btnFullRemote.addTarget(self, action: #selector(self.btnFullRemote(_:)), for: .touchUpInside)
step 4: typecast the sender in receiving function to a custom object and access those properties
#objc public func btnFullRemote(_ sender: Any) {
var name : String = (sender as! CustomButton).name as? String
var customObject : customObject = (sender as! CustomButton).customObject as? customObject
var customObject2 : customObject2 = (sender as! CustomButton).customObject2 as? customObject2
}
Swift 5.0 iOS 13
I concur a great answer by Ninad. Here is my 2 cents, the same and yet different technique; a minimal version.
Create a custom class, throw a enum to keep/make the code as maintainable as possible.
enum Vs: String {
case pulse = "pulse"
case precision = "precision"
}
class customTap: UITapGestureRecognizer {
var cutomTag: String?
}
Use it, making sure you set the custom variable into the bargin. Using a simple label here, note the last line, important labels are not normally interactive.
let precisionTap = customTap(target: self, action: #selector(VC.actionB(sender:)))
precisionTap.customTag = Vs.precision.rawValue
precisionLabel.addGestureRecognizer(precisionTap)
precisionLabel.isUserInteractionEnabled = true
And setup the action using it, note I wanted to use the pure enum, but it isn't supported by Objective C, so we go with a basic type, String in this case.
#objc func actionB(sender: Any) {
// important to cast your sender to your cuatom class so you can extract your special setting.
let tag = customTag as? customTap
switch tag?.sender {
case Vs.pulse.rawValue:
// code
case Vs.precision.rawValue:
// code
default:
break
}
}
And there you have it.
cell.btn.tag = indexPath.row //setting tag
cell.btn.addTarget(self, action: #selector(showAlert(_ :)), for: .touchUpInside)
#objc func showAlert(_ sender: UIButton){
print("sender.tag is : \(sender.tag)")// getting tag's value
}
Just create a custom class of UITapGestureRecognizer =>
import UIKit
class OtherUserProfileTapGestureRecognizer: UITapGestureRecognizer {
let userModel: OtherUserModel
init(target: AnyObject, action: Selector, userModel: OtherUserModel) {
self.userModel = userModel
super.init(target: target, action: action)
}
}
And then create UIImageView extension =>
import UIKit
extension UIImageView {
func gotoOtherUserProfile(otherUserModel: OtherUserModel) {
isUserInteractionEnabled = true
let gestureRecognizer = OtherUserProfileTapGestureRecognizer(target: self, action: #selector(self.didTapOtherUserImage(_:)), otherUserModel: otherUserModel)
addGestureRecognizer(gestureRecognizer)
}
#objc internal func didTapOtherUserImage(_ recognizer: OtherUserProfileTapGestureRecognizer) {
Router.shared.gotoOtherUserProfile(otherUserModel: recognizer.otherUserModel)
}
}
Now use it like =>
self.userImageView.gotoOtherUserProfile(otherUserModel: OtherUserModel)
You can use an UIAction instead:
self.imageView.addAction(UIAction(identifier: UIAction.Identifier("imageClick")) { [weak self] action in
self?.handleTap(modelObj)
}, for: .touchUpInside)
that may be a terrible practice but I simply add whatever I want to restore to
button.restorationIdentifier = urlString
and
#objc func openRelatedFact(_ sender: Any) {
if let button = sender as? UIButton, let stringURL = factButton.restorationIdentifier, let url = URL(string: stringURL) {
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [:])
}
}
}

Swift: Accept a closure with varying parameters

Suppose I have a function that accepts a callback with a sender, like this:
func performAction(aNumber: Double, completion: (sender: UIButton) -> Void) {
// Does some stuff here
let button = getAButtonFromSomewhere()
completion(button)
}
And so one possible way to call this function is by passing an existing function for the callback, rather than defining the closure in-place:
performAction(10, completion: myCallback)
func myCallback(sender: UIButton) {
sender.setTitle("foo", forState: .Normal)
}
Back in my definition for performAction, how can I define the completion block to accept a UIButton or any subclass of it?
As an example, suppose I have a UIButton subclass called CustomButton. So in my callback, I'm only interested in accepting a CustomButton. I'd like to do this:
performAction(10, completion: myCallback)
// This produces a compiler error:
func myCallback(sender: CustomButton) {
sender.setTitle("foo", forState: .Normal)
}
// This works, but forces me to cast to my custom class:
func myCallback(sender: UIButton) {
let realButton = sender as! CustomButton
realButton.setTitle("foo", forState: .Normal)
}
But the compiler won't allow it, because the definition of performAction requires the callback to accept a UIButton specifically (even though CustomButton is a UIButton subclass).
I'd like performAction to be generic so that it can be packaged in a library, and work with any UIButton subclass. Is this possible to do in Swift?
EDIT: I tried to simplify what I'm doing with the example above, but I think it just caused confusion. Here's the actual code that I'm trying to make work, with some improvements thanks to #luk2302:
public extension UIButton {
private class Action: AnyObject {
private var function: Any
init(function: Any) {
self.function = function
}
}
// Trickery to add a stored property to UIButton...
private static var actionsAssocKey: UInt8 = 0
private var action: Action? {
get {
return objc_getAssociatedObject(self, &UIButton.actionsAssocKey) as? Action
}
set(newValue) {
objc_setAssociatedObject(self, &UIButton.actionsAssocKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}
internal func performAction(sender: UIButton) {
if let function = self.action!.function as? () -> Void {
function()
// THIS IS WHERE THINGS BREAK NOW:
} else if let function = self.action!.function as? (sender: self.Type) -> Void {
function(sender: self)
}
}
public func addTarget(forControlEvents event: UIControlEvents, action: () -> Void) {
self.action = Action(function: action)
self.addTarget(self, action: "performAction:", forControlEvents: event)
}
public func addTarget<B: UIButton>(forControlEvents: UIControlEvents, actionWithSender: (sender: B) -> Void) {
self.action = Action(function: actionWithSender)
self.addTarget(self, action: "performAction:", forControlEvents: forControlEvents)
}
}
The only piece that breaks now is the line that I commented, at (sender: self.Type) (self being either UIButton, or some subclass of it).
So this deviates from my original question slightly, but how can I can I cast function to a closure accepting a sender of the same type as self? This code works perfectly if I hard-code the type, but it should be able to work for any UIButton subclass.
You can make the UIButton subclass a generic parameter for the performAction function, but then you will need to cast the button before passing it to the callback, unless you also have a generic way of "getting" the right type of button.
// performAction() works with any type of UIButton
func performAction<B: UIButton>(aNumber: Double, completion: (sender: B) -> Void)
{
// Assuming getAButtonFromSomewhere returns UIButton, and not B, you must cast it.
if let button = getAButtonFromSomewhere() as? B {
completion(sender: button)
}
}
Ask yourself this: what should happen if you pass a closure of type (CustomButton -> Void) as completion and then getAButtonFromSomewhere returns an instance of UIButton? The code then cannot invoke the closure since the UIButton is not a CustomButton.
The compiler simply does not allow you to pass (CustomButton -> Void) to (UIButton -> Void) because (CustomButton -> Void) is more restrictive than (UIButton -> Void). Note that you can pass (UIButton -> Void) to a closure of type (CustomButton -> Void) since (UIButton -> Void) is less restrictive - you can pass everything you pass to the first to the second as well.
Therefore either make func use a generic type as #jtbandes suggests or use your initial approach a little bit improved:
func myCallback(sender: UIButton) {
if let realButton = sender as? CustomButton {
realButton.setTitle("foo", forState: .Normal)
}
}
Both solutions will result in the setTitle to not be invoked whenever the returned value of getAButtonFromSomewhere is not a CustomButton.

How stop another function from an UIButton function using Swift?

I got two functions running called updatePrice and updateTime, and I want to stop them when I click an UIButton.
func updateTime() {...}
func updatePrice() {...}
// ..
#IBAction func Button(sender: UIButton) {
// What should I write here?
}
Do something like NSOperations do: make a boolean and periodically check it in those functions. Something like:
var stopFuncs = false
and later in functions at sensitive moments insert that:
if stopFuncs { return }
and add this to button code:
stopFuncs = true

Calling IBAction from another method without parameter

In my program 2 functions (IBAction player.Move(UIButton) and autoMove()) are supposed to be called by turns till all of the fields (UIButtons) has been clicked. For this I've created a function play(). However, I don't know how can I put the IBAction playerMove inside of play() function, because I need no parameter here.
I've found some answers and tried self.playerMove(nil) and self.playerMove(self) but it doesn't work.
import UIKit
class ViewController: UIViewController {
#IBOutlet var cardsArray: Array<UIButton> = []
var randomCard = 0
override func viewDidLoad() {
super.viewDidLoad()
self.play()
// Do any additional setup after loading the view, typically from a nib.
}
func play () {
self.autoMove()
self.playerMove(self) // <----- here is my problem
}
#IBAction func playerMove(sender: UIButton) {
switch (sender) {
case self.cardsArray[0]:
self.cardPressedAll(0)
case self.cardsArray[1]:
self.cardPressedAll(1)
case self.cardsArray[2]:
self.cardPressedAll(2)
case self.cardsArray[3]:
self.cardPressedAll(3)
default: break
}
}
func cardPressedAll (cardNumber: Int) {
self.cardsArray[cardNumber].enabled = false
self.cardsArray[cardNumber].setBackgroundImage(UIImage(named: "cross"), forState: UIControlState.Normal)
self.cardsArray.removeAtIndex(cardNumber)
}
func autoMove (){
self.randomCard = Int(arc4random_uniform(UInt32(self.cardsArray.count)))
self.cardsArray[self.randomCard].enabled = false
self.cardsArray[self.randomCard].setBackgroundImage(UIImage(named: "nought"), forState: UIControlState.Normal)
self.cardsArray.removeAtIndex(self.randomCard)
}
}
Either you have to call playerMove: without a button, in which case you have to declare the sender parameter as an optional. Like:
#IBAction func playerMove(sender: UIButton?) {
UIButton means that you have to pass in a button. nil is not a button, but with UIButton?, that is to say Optional<UIButton>, nil is a valid value meaning the absence of a button.
Or you have to work out which button you want to pass to playerMove: to make it do what you want. Sit down and work out what you want to have happen, and what the code needs to do in order to make that happen.
Try
self.playerMove(UIButton())
Your func playerMove has parameters expecting sender to be of type UIButton, self or nil would be an unexpected object.
Edit:
You could us optional parameters by placing ?. This would allow you to call self.playerMove(nil) if needed.
#IBAction func playerMove(sender: UIButton?) {
if sender != nil {
//handle when button is passed
} else {
//handle when nil is passed
}
}
doSomeTask(UIButton()) in swift 5.0 and onward worked for me

Resources