I am trying to create dynamic radio buttons based on Firebase data. I am hoping to arrange the buttons in a vertical stack, and essentially when they are clicked I would hope for them to disappear and display another view:
class PollController: UIViewController {
#IBOutlet weak var passLabel: UILabel!
#IBOutlet weak var pollImage: UIImageView!
var ref: FIRDatabaseReference!
var pollRef: FIRDatabaseReference!
var pass = ""
var passedImageURL = ""
var posX = 0;
var posY = 0;
var buttons = [UIButton]()
override func viewDidLoad() {
super.viewDidLoad()
ref = FIRDatabase.database().reference()
pollRef = ref.child("Polls").child(pass)
passLabel.text = pass
pollImage.sd_setImage(with: URL(string: passedImageURL), placeholderImage: UIImage(named: "test"))
pollRef.observe(FIRDataEventType.value, with: {(snapshot) in
let numberOfChildren = snapshot.childSnapshot(forPath: "answers").childrenCount
self.passLabel.text = String(numberOfChildren)
print(numberOfChildren)
var stackView = UIStackView(arrangedSubviews: self.buttons)
// create button1
for x in 0..<numberOfChildren {
let button = UIButton(frame: CGRect(x: self.posX, y: self.posY, width: 60, height: 20))
button.setTitleColor(UIColor.black, for: .normal)
button.setTitle("No", for: .normal)
button.setImage(UIImage(named: "checkbox untick.png")!, for: .normal)
// if the selected button cannot be reclick again, you can use .Disabled state
button.setImage(UIImage(named: "checkboxredtick.png")!, for: .selected)
button.tag = Int(x)
button.addTarget(self, action: #selector(self.buttonAction(sender:)), for: .touchUpInside)
stackView.addSubview(button)
self.buttons.append(button)
// create other buttons and add into buttons ...
}
})
// Do any additional setup after loading the view.
}
func buttonAction(sender: UIButton!){
for button in buttons {
button.isSelected = false
}
sender.isSelected = true
// you may need to know which button to trigger some action
// let buttonIndex = buttons.indexOf(sender)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
If you are using SWIFT 3.0 or later,
Then you must define add target method like below as _: is missing in the selector parameter.
button.addTarget(self, action: #selector(buttonAction(_:)), for: .TouchUpInside)
Also check id you have declares myStackview variable here. if you declared then try to add it as :
self.myStackview
Your button declaration is func buttonAction(sender: UIButton!). Note that there are no underscore before sender. This means that you need to specify the name of the argument when you call the function. Replace the underscore in the selector with the argument name sender
button.addTarget(self, action: #selector(buttonAction(sender:)), for: .touchUpInside)
You can also add an underscore in front of the argument name in the function.
func buttonAction(_ sender: UIButton!)
Also, I noticed that the code is in Swift 3 seeing most of the syntax. So, the control event for touch up inside in Swift 3 is touchUpInside and not TouchUpInside
Related
The following code is located inside a subclass of UIView
I am setting up a cancelButton inside a closure:
private var cancelButtonClosure: UIButton = {
...
button.addTarget(self, action: #selector(cancel(_:)), for: .touchUpInside)
...
}()
And at first I instantiated the button inside a function like so:
func showConfirmationView(...) {
...
let cancelButton = self.cancelButtonClosure
...
addSubview(cancelButton)
...
}
However this resulted in the cancel function not being called at all (even though the layout was right and the button was highlighting)
So I made these change:
Removed the addTarget part from the cancelButtonClosure
Added the addTarget part inside the showConfirmationView function
So it looked like that:
func showConfirmationView(...) {
...
let cancelButton = self.cancelButtonClosure
cancelButton.addTarget(self, action: #selector(cancel(_:)), for: .touchUpInside)
...
addSubview(cancelButton)
...
}
It worked: the cancel function was called; but I don't know why. I'm really curious to know why what I did before did not work. Thanks for your insights!
Check your implementation because a setup like this works as expected:
private var cancelButton: UIButton = {
let btn = UIButton(type: .system)
btn.setTitle("Cancel", for: .normal)
btn.addTarget(self, action: #selector(cancelSomething(_:)), for: .touchUpInside)
return btn
}()
#objc func cancelSomething(_ sender: UIButton) {
print("Something has to be cancelled")
}
override func viewDidLoad() {
super.viewDidLoad()
showConfirmationView()
}
func showConfirmationView() {
cancelButton.sizeToFit()
cancelButton.center = view.center
view.addSubview(cancelButton)
}
I have a class where written is a function creating my button:
LoginButton.swift
func createButton() {
let myButton: UIButton = {
let button = UIButton()
button.addTarget(self, action: #selector(Foo().buttonPressed(_:)), for: .touchUpInside)
}()
}
Now in my second class, Foo.swift, I have a function that just prints a statement
Foo.swift
#objc func buttonPressed(_ sender: UIButton) {
print("button was pressed")
}
When ran I get no errors except when I try to press the button, nothing happens. Nothing prints, the UIButton doesn't react in any way. Really not sure where the error occurs because Xcode isn't printing out any type of error or warning message.
The action method is called in the target object. Thus, you have either to move buttonPressed to the class which contains createButton or to pass an instance of Foo as a target object.
But note that a button is not the owner of its targets. So, if you just write:
button.addTarget(Foo(), action: #selector(buttonPressed(_:)), for: .touchUpInside)
This will not work, because the Foo object is immediately released after that line. You must have a strong reference (e.g. a property) to Foo() like
let foo = Foo()
func createButton() {
let myButton: UIButton = {
let button = UIButton()
button.addTarget(foo, action: #selector(buttonPressed(_:)), for: .touchUpInside)
}()
}
You are missing with target. So make instant of target globally and make use of it as target for button action handler.
class ViewController: UIViewController {
let foo = Foo()
override func viewDidLoad() {
super.viewDidLoad()
createButton()
}
func createButton() {
let myButton: UIButton = {
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 50))
button.backgroundColor = UIColor.red
button.setTitle("Tap me", for: .normal)
button.addTarget(self.foo, action: #selector(self.foo.buttonPressed(_:)), for: .touchUpInside)
return button
}()
myButton.center = self.view.center
self.view.addSubview(myButton)
}
}
Class Foo:
class Foo {
#objc func buttonPressed(_ sender: UIButton) {
print("button was pressed")
}
}
Just pass Selector as function argument.
func createButtonWith(selector: Selector) {
let myButton: UIButton = {
let button = UIButton()
button.addTarget(self, action: selector), for: .touchUpInside)
}()
}
And call this function like below...
createButtonWith(selector: #selector(Foo().buttonPressed(_:)))
SquareBox.swift
class SquareBox {
func createBoxes() {
for _ in 0..<xy {
let button = UIButton()
button.backgroundColor = .white
button.setTitleColor(UIColor.black, for: .normal)
button.layer.borderWidth = 0.5
button.layer.borderColor = UIColor.black.cgColor
stack.addArrangedSubview(button)
button.addTarget(self, action: #selector(click(sender:)) , for: .touchUpInside)
}
}
#objc func click(sender : UIButton) {
print("Click")
}
}
ViewController.swift
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let boxRow = SquareBox()
boxRow.createBoxes()
}
}
Also I've tried #IBAction instead of #objc, it doesn't work, but if I use "click" function in ViewController.swift that I created this object, it's working but I need this function inside of this class.
Now that you have posted relevant information in your question, the problem is quite clear. You have a memory management issue.
In your GameViewController's viewDidLoad you create a local instance of SquareBox. This local instance goes out of scope at the end of viewDidLoad. Since there is no other reference to this instance, it gets deallocated at the end of viewDidLoad.
Since the instance of SquareBox has been deallocated, it is not around to act as the button's target. And your click method is never called.
The solution is to keep a reference in your view controller:
class GameViewController: UIViewController {
let boxRow = SquareBox()
override func viewDidLoad() {
super.viewDidLoad()
boxRow.createBoxes()
}
}
var btnfirst:UIButton!
override func viewDidLoad()
{
super.viewDidLoad()
btnfirst = UIButton(type: .system)
btnfirst.setTitle("Press", for: .normal)
btnfirst.setTitleColor(.red, for: .normal)
btnfirst.frame = CGRect(x: 100, y: 200, width: 100, height: 30)
btnfirst.addTarget(self, action: #selector(benpress( sender:)),for: .touchUpInside)
self.view.addSubview(btnfirst)
}
func benpress( sender :UIButton)
{
//Your Code Here
}
For those who did not find a solution, here is mine.
If you constructed your UIButton as
let button: UIButton = {
return UIButton()
}()
Just convert those into
lazy var button: UIButton = {
return UIButton()
}()
I think this is because of somewhat deallocation as mentioned above.
button.addTarget(self, action:#selector(self.click), for: .touchUpInside)
func click(sender : UIButton) {
// code here
}
I guess the issue is how you are setting up layout of your buttons.
Try this:
func createBoxes() {
stack.backgroundColor = UIColor.red
for _ in 0..<xy {
// Create the button
let button = UIButton()
button.backgroundColor = UIColor.red
// Add constraints
button.translatesAutoresizingMaskIntoConstraints = false
button.heightAnchor.constraint(equalToConstant: 44.0).isActive = true
button.widthAnchor.constraint(equalToConstant: 44.0).isActive = true
// Setup the button action
button.addTarget(self, action: #selector(SquareBox.click(sender:)), for: .touchUpInside)
// Add the button to the stack
stack.addArrangedSubview(button)
}
}
#objc func click(sender : UIButton) {
print("Click")
}
button.addTarget(self, action: #selector(self.buttonTapped), for: .touchUpInside)
func buttonTapped(sender : UIButton) {
// code here
}
Replace with this :
btn.addTarget(self, action: #selector(self.click(sender:)), for: .touchUpInside)
I think something else effect to your selector method try to find in your code because your code also working in my project.
I am trying to create radio buttons programmatically based on data in Firebase. The Firebase data will essentially be a number, and then I plan on using that number in a for loop to populate the necessary number of radio buttons. I have previous experience in Android and am trying to translate to swift:
import UIKit
import FirebaseDatabase
class PollController: UIViewController {
#IBOutlet weak var passLabel: UILabel!
#IBOutlet weak var pollImage: UIImageView!
var ref: FIRDatabaseReference!
var pollRef: FIRDatabaseReference!
var pass = ""
var passedImageURL = ""
var posX = 0;
var posY = 0;
override func viewDidLoad() {
super.viewDidLoad()
ref = FIRDatabase.database().reference()
pollRef = ref.child("Polls").child(pass)
passLabel.text = pass
pollImage.sd_setImage(with: URL(string: passedImageURL), placeholderImage: UIImage(named: "test"))
pollRef.observe(FIRDataEventType.value, with: {(snapshot) in
let numberOfChildren = snapshot.childSnapshot(forPath: "answers").childrenCount
self.passLabel.text = String(numberOfChildren)
print(numberOfChildren)
var buttons = [UIButton]()
// create button1
for x in 0..<numberOfChildren {
let button = UIButton(frame: CGRect(x: self.posX, y: self.posY, width: 60, height: 20))
button.setTitleColor(UIColor.black, for: .normal)
button.setTitle("No", for: .normal)
button.setImage(UIImage(named: "checkbox untick.png")!, for: .normal)
// if the selected button cannot be reclick again, you can use .Disabled state
button.setImage(UIImage(named: "checkboxredtick.png")!, for: .selected)
button.tag = Int(x)
button.addTarget(self, action: #selector(buttonAction(_:))), forControlEvents: .TouchUpInside)
myStackview.addSubview(button)
buttons.append(button)
// create other buttons and add into buttons ...
}
func buttonAction(_ sender: UIButton!){
for button in buttons {
button.isSelected = false
}
sender.isSelected = true
// you may need to know which button to trigger some action
// let buttonIndex = buttons.indexOf(sender)
}
})
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
EDIT: I was able to narrow down the issue, however I am still having some issues understanding selectors and senders. It seems that's where my error is coming in, as there is a red exclamation stating "Expected Expression" on the line of my #selector.
It would help if you spend some time to learn the language instead of translating it statement by statement. If you want a quick way (not ideal) refer to the syntax of statements / lines with a with red exclamation mark
For statement
for (x in 0 ..< numberOfChildren) { }
Swift uses type inference, it determines the type based on the value assigned.
Selector
It needs to be the name of the function with function argument labels on it
myStackView
Refer UIStackView
addArrangedSubview
buttons
Learn Swift Optionals
In my swift project I have a button and I want to print on a label how much time this button has been pressed.
How can I resolve this problem?
Adding to DHEERAJ's answer, you just need to add following code in func pressed(sender: UIButton!) to achieve what you need:
let date = NSDate()
let dateFormatter = NSDateFormatter()
dateFormatter.dateStyle = .MediumStyle
let dateString = dateFormatter.stringFromDate(date)
yourLabelName.text = dateString
Hope this might help.
To show how many times the button has been clicked you could class variable.
class SampleViewController: UIViewController {
var counter : Int!
#IBOutlet weak var yourLabelName: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
counter = 1
let myFirstButton = UIButton(frame: CGRectMake(10, 50, 320, 40))
myFirstButton.setTitle("Tap Me", forState: .Normal)
myFirstButton.setTitleColor(UIColor.blueColor(), forState: .Normal)
myFirstButton.addTarget(self, action: "pressed:",forControlEvents: .TouchUpInside)
self.view.addSubview(myFirstButton)
}
func pressed(sender: UIButton!)
{
yourLabelName.text = "Pressed : \(counter)"
counter = counter + 1
}
}
Here is how button is created programmatically and prints a text in label when button is pressed.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let myFirstButton = UIButton()
myFirstButton.setTitle("Tap Me", forState: .Normal)
myFirstButton.setTitleColor(UIColor.blueColor(), forState: .Normal)
myFirstButton.frame = CGRectMake(10, 50, 320, 40)
myFirstButton.addTarget(self, action: "pressed:", forControlEvents: .TouchUpInside)
self.view.addSubview(myFirstButton)
}
func pressed(sender: UIButton!)
{
yourLabelName.text=#"Your Text is here"
}