Is it possible to use an array entry in a #selector? - swift

I'm adding labels and buttons to a TextView dynamically. This is driven by looping through arrays containing label names, button names, and button function names. For brevity and simplicity, I've changed the code here to use hard-coded indexes rather than a loop, and I did not include the label- and button-creation functions. The labels are created just fine. The first button is also created just fine, since I specify Button1Clicked as the #selector argument. The second button is created correctly only if I specify Button2Clicked as the #selector argument. If I attempt to reference an array in the button #selector argument, I receive the compiler error "Argument of '#selector' does not refer to an '#objc' method, property, or initializer". Assuming it is possible to use an array containing the #objc function name as an argument to the #selector, can someone please provide the appropriate syntax for me? I'm using Swift 5 and Xcode 11.2. Thank you in advance for your assistance.
#objc func Button1Clicked(sender: UIButton) {
print("You pressed button 1")
}
#objc func Button2Clicked(sender: UIButton) {
print("You pressed button 2")
}
.
.
.
var labelNames = [UILabel]()
let Label1:UILabel = UILabel()
let Label2:UILabel = UILabel()
labelNames = [Label1, Label2]
var buttonNames = [UIButton]()
let Button1:UIButton = UIButton()
let Button2:UIButton = UIButton()
buttonNames = [Button1, Button2]
let buttonSelectorNames = ["Button1Clicked", "Button2Clicked"]
configureLabel(labelNameIn: labelNames[0],
textIn: "aaaaa",
textColorIn: appColorWhite,
backgroundColorIn: appColorBlue,
yPositionIn: 0)
statusTextview.addSubview(labelNames[0])
configureLabel(labelNameIn: labelNames[1],
textIn: "-- bbbbb",
textColorIn: appColorWhite,
backgroundColorIn: appColorBlue,
yPositionIn: 25)
statusTextview.addSubview(labelNames[1])
configureButton(buttonNameIn: buttonNames[0],
xPositionIn: 0,
yPositionIn: 50,
textIn: "B1",
textColorIn: appColorBlack,
backgroundColorIn: appColorBrightGreen,
textViewIn: statusTextview)
buttonNames[0].addTarget(self,
action: #selector(Button1Clicked),
for: .touchUpInside)
statusTextview.addSubview(buttonNames[0])
configureButton(buttonNameIn: buttonNames[1],
xPositionIn: 100,
yPositionIn: 50,
textIn: "B2",
textColorIn: appColorBlack,
backgroundColorIn: appColorBrightGreen,
textViewIn: statusTextview)
buttonNames[1].addTarget(self,
action: #selector(buttonSelectorNames[1]),
for: .touchUpInside)
statusTextview.addSubview(buttonNames[1])

you don’t need to define button click event for each button.you can define common function and assign it to all buttons in the loop.then you can specify through button tag
in your loop assign the current index as button tag
buttonNames[currentIndex].tag = currentIndex
buttonNames[currentIndex].addTarget(self,
action: #selector(didButtonClicked(_ :)),
for: .touchUpInside)
button click function
#objc func didButtonClicked(_ sender : UIButton){
switch sender.tag{
case 0: //clicked button with tag 0
case 1: //clicked button with tag 1
default: break
}
}

In the past, I have a deal with the same type of requirement. I was doing it by storing the selector in Array.
Please refer to the code snippets. I am sure it will help you.
Code:
class ViewController: UIViewController {
let selector = [#selector(Button1Clicked),#selector(Button2Clicked)]
override func viewDidLoad() {
super.viewDidLoad()
let btn = UIButton(frame: self.view.bounds)
self.view.addSubview(btn)
btn.addTarget(self, action: selector[0], for: .touchUpInside)
}
#objc func Button1Clicked(){
print("Button1Clicked")
}
#objc func Button2Clicked(){
}
}
Output:

Related

How to programmatically add tags to a UIButton in Swift

I know how to add tags in main.storyboard files, but now I am working on creating everything programmatically. However, I couldn't find anything online that tells me how to add tags to a button. I tried to read the apple documentation but don't know how to implement it.
Here is for example how I created my button, how can I add a tag to it, so that when I make multiple buttons and want to link them to a single action I can know what button has been pressed?
let buttonOne() : UIButton {
let button = UIButton(type: .system)
button.backgroundColor = .red
return button
}()
Try this:
let buttonOne() : UIButton {
let button = UIButton(type: .system)
button.backgroundColor = .red
button.tag = 2
return button
}()
Declare The Button
let menuChooseButton = UIButton()
menuChooseButton.frame.origin.y = 0
menuChooseButton.frame.origin.x = xPosition
menuChooseButton.frame.size.width = subViewWidth
menuChooseButton.frame.size.height = subViewHeight
menuChooseButton.backgroundColor = .clear
menuChooseButton.setTitle("", for: .normal)
menuChooseButton.tag = i
menuChooseButton.addTarget(self, action: #selector(menuSelectAction), for: .touchUpInside)
menuScrollView.addSubview(menuChooseButton)
Define Button Action
#objc func menuSelectAction(sender: UIButton!){
var tagNo: Int = sender.tag
if tagNo == 0{
// Whatever you want
}
}

Storing and Calling Index Path Swift

I have a collection view in swift in where it needs to store which button is pressed last. These buttons consist of A, B, C, D, this is like a quiz game.
Code:
extension QuestionCell {
func configure(with model: QuestionCellModel) {
factLabel.text = model.fact
questionLabel.text = model.question
let views = answersStack.arrangedSubviews
for view in views {
view.removeFromSuperview()
}
for (index, answer) in model.answers.enumerated() {
print(index)
let answerLabel = UILabel()
answerLabel.text = answer
answersStack.addArrangedSubview(answerLabel)
let answerButton = UIButton()
answerButton.tag = index
let imageNormal = UIImage(named: "circle_empty")
answerButton.setImage(imageNormal, for: .normal)
let imageSelected = UIImage(named: "circle_filled")
answerButton.setImage(imageSelected, for: .selected)
answerButton.setTitleColor(.black, for: .normal)
answerButton.addTarget(self, action: #selector(answerPressed(_:)), for: .touchUpInside)
answersStack.addArrangedSubview(answerButton)
}
}
So with this it is able to print the index, but it should know which was the last button they selected, when they were done (Buttons: A,B,C,D). Based on the index it should store assigned values of each collection view cell(There are 5 question in the quiz). I have not figured out a way on how to solve this question because I am not sure the computer will store multiple index's in the collection view(Like how would the computer know when you click the done with quiz button what was the last index it chose for each question?) I hope this makes sense!
Thanks

Swift: connect a static button with a static method

Say I have a static button member variable, as well as a static method which handles its click. How can I connect the button with the method? I couldn't figure out how to get addTarget to work in this case:
private static let my_button: UIButton = {
let button = UIButton(type: .system)
...
button.addTarget(???, action: #selector(handleButtonClick), for: .touchUpInside)
return button
}()
private static func handleButtonClick() {
...
}
Could I put UIApplication.shared.keyWindow!.rootViewController in place of ????
Details
xCode 8.2.1, Swift 3
Full sample
import UIKit
class Button:NSObject {
class func createButton() -> UIButton {
let button = UIButton(type: .system)
button.frame = CGRect(x: 40, y: 40, width: 200, height: 40)
button.setTitle("Text", for: .normal)
button.addTarget(self, action: #selector(Button.handleButtonClick), for: .touchUpInside)
return button
}
class func handleButtonClick() {
print("Click")
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
view.addSubview(Button.createButton())
}
}
Result
You have to have an actual object - an instance of your class - as the button's target. It might be possible to pass the class object, but that's not how it's supposed to work. The button basically expects its target to be an object that inherits from NSResponder.

pass custom parameter to uibutton #selector swift 3

I have a 2 classes where I am passing uistackviews from one class to other. I want the controls to be created in same stackview. Hence I am passing the view in all the render function parameters. I also want that view to be passed with action #selector of uibutton
class 1:
class ViewController: UIViewController {
func createbutton(parentview: UIStackView) {
let buttn = UIButton()
buttn.backgroundColor = .gray
buttn.setTitle("testttt", for: .normal)
buttn.frame.size.height = 30
buttn.frame.size.width = 40
buttn.addTarget(self, action: #selector(anotherbutton(parentview:)), for: .touchUpInside)
parentview.addArrangedSubview(buttn)
}
func anotherbutton(parentview: UIStackView) {
//another button here
}
func loadpage() {
print("loadpage")
}
}
Class 2:
class plugin : UIViewController {
let vw = ViewController()
override func viewDidLoad() {
super.viewDidLoad()
let parentview = getparentnode()
vw.createbutton(parentview: parentview)
}
func getparentnode() -> UIStackView {
let parentnode = UIStackView()
parentnode.axis = UILayoutConstraintAxis.vertical
parentnode.distribution = UIStackViewDistribution.equalSpacing
parentnode.alignment = UIStackViewAlignment.center
parentnode.spacing = 16.0
parentnode.tag = 50
parentnode.translatesAutoresizingMaskIntoConstraints = false;
self.view.addSubview(parentnode)
//Constraints
parentnode.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
parentnode.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
return parentnode
}
}
but this throws an error unrecognized selector sent to instance 0x7b25e010'
How to pass the UIView in action selector parameter ? Thank you for any help
You can't. The only things that you can pass through a selector is:
Nothing
The object itself (in this case the button)
These scenarios would look like this:
button.addTarget(self, action: #selector(myFunc), ...) //no parameters
or
button.addTarget(self, action: #selector(myFunc(_:)) //passes itself (the button)
If you want to pass the value of a view to another ViewController I recommend using the prepareForSegue method. That is how you are supposed to pass data from ViewController to ViewController.
In terms of the rest of your code, I believe you are breaking the MVC design pattern by creating an instance of your class in another class (this line: let vw = ViewController()). First of all, this will create an entirely new instance if your ViewController, which isn't the same as the one running on your device. Second of all, this is bad practice. You should be allowing each viewController to manage itself and not have outwards interference from other viewControllers. Using prepareForSegue is an example of using the MVC design pattern effectively.
Hope this helped.

how to change bordercolor when Button.isHighlighted

I wonder how I get my border around my UIButton to change opacity together with the text inside it, when it is either clicked or highlighted.
My logic tells me, that it should be something like this.. but it doesn't seem to work:
//BTN STYLING
btnstd.layer.cornerRadius = 5
btnstd.layer.borderWidth = 1.5
btnstd.layer.borderColor = UIColor.white.cgColor
//Change bordercolor when highlighted
if(btnstd.isHighlighted) {
btnstd.layer.borderColor = UIColor(white:1,alpha:0.3).cgColor
}
This is by the way put inside my ViewDidLoad() function
The actions you are looking for are .touchDown and anything .touchUp:
override func viewDidLoad() {
super.viewDidLoad()
theButton.setTitle("Normal", for: .normal)
theButton.setTitle("Highlighted", for: .highlighted)
theButton.layer.borderColor = UIColor.red.cgColor
theButton.layer.borderWidth = 1
theButton.addTarget(self, action: #selector(startHighlight), for: .touchDown)
theButton.addTarget(self, action: #selector(stopHighlight), for: .touchUpInside)
theButton.addTarget(self, action: #selector(stopHighlight), for: .touchUpOutside)
}
func startHighlight(sender: UIButton) {
theButton.layer.borderColor = UIColor.green.cgColor
theButton.layer.borderWidth = 1
}
func stopHighlight(sender: UIButton) {
theButton.layer.borderColor = UIColor.red.cgColor
theButton.layer.borderWidth = 1
}
It depends on what you are trying to do.
Case #1: You want this change to happen when the button is highlighted, but in a normal state have a different set of properties.
let theButton = UIButton()
// set common properties and layout code
theButton.setTitle("Normal", for: .normal)
theButton.setTitle("Highlighted", for: .highlighted)
In addition, you have setTitleColor(), setAttributedTitle, setTitleShadowColor(), setImage(), and setBackgroundImage() that you can code directly.
Border color in this case would need a subclass (not an extension, you want public properties) where you will check self.layer.hitTest() after wiring up a tap gesture on self.
Case #2: You want the button state to change when clicked, and stay changed.
You are part way there. If you supply the button in IB, make sure you add an IBAction for event touchUpInside. If you are working in code, here's the Swift 3 syntax.
theButton.addTarget(self, action: #selector(changeButton), for: .touchUpInside)
func changeButton(sender: UIButton) {
sender.setTitle("New Title", for: .normal)
sender.layer.borderColor = UIColor.red.cgColor
}
My preference (but only that) is to more strongly-type the sender (I think that's the correct term) for my actions. I'm sure there are pros and cons for using a specific sender (like UIButton) over AnyObject, but in this case I think the biggest reason is you don't need to force-cast the sender to UIButton.