How create buttonAction functions dynamically in Swift - ios

In a MCQ app I create multiple UIButton programatically like this
func createNewButton(buttonName: String, xPosition: CGFloat, yPosition: CGFloat) -> UIButton {
let myButton = UIButton(frame: CGRectMake(0, 0, 50, 50))
myButton.center = CGPoint(x: xPosition, y: yPosition)
myButton.setImage(UIImage(named: buttonName), forState: UIControlState.Normal)
myButton.addTarget(self, action: "buttonName:", forControlEvents: UIControlEvents.TouchUpInside)
return myButton
}
The problem is that in order to match a function to these buttons I need to create a function. The function should therefore looks like
func buttonName(sender: UIButton!) {
// do some stuff
}
These functions are actually going to save a string in an array, and the string to save is the name of the function itself. If we have for example buttonName: "blackberry", the code is going to set an image called "blackberry" and add a target action named "blackberry" and I would like therefore a buttonAction function called func blackberry(sender: UIButton) and this function would save the string "blackberry" in an array of String.
But all of this should be done in function of the initial buttonName: String parameter.
Or maybe I am really not doing things the right way, and how should I do then?

How about this instead. Send all of your buttons to processButton(button: UIButton). When you create your buttons, set the tag property assigning a unique number to each button. Then you can have an array which translates the button tag to the string.
var buttonNames: [String] = []
var currentIndex = 0
func createNewButton(buttonName: String, xPosition: CGFloat, yPosition: CGFloat) -> UIButton {
let myButton = UIButton(frame: CGRectMake(0, 0, 50, 50))
myButton.center = CGPoint(x: xPosition, y: yPosition)
myButton.setImage(UIImage(named: buttonName), forState: UIControlState.Normal)
myButton.addTarget(self, action: "processButton:", forControlEvents: UIControlEvents.TouchUpInside)
myButton.tag = currentIndex++
self.buttonNames.append(buttonName)
return myButton
}
func processButton(button: UIButton) {
let string = buttonNames[button.tag]
// process string
}

Related

Assign tags for dynamic UIButtons

I found this example of a dynamic view of UIButtons at: http://helpmecodeswift.com/advanced-functions/generating-uibuttons-loop
I inserted this code into my app but unfortunately I can't find a way to set a tag for each buttons.
My target is to know which button is tapped, count the amount of clicks for each button and save this information into my DB.
override func viewDidLoad() {
super.viewDidLoad()
var arrayOfVillains = ["santa", "bugs", "superman", "batman"]
var buttonY: CGFloat = 20
for villain in arrayOfVillains {
let villainButton = UIButton(frame: CGRect(x: 50, y: buttonY, width: 250, height: 30))
buttonY = buttonY + 50 // we are going to space these UIButtons 50px apart
villainButton.layer.cornerRadius = 10 // get some fancy pantsy rounding
villainButton.backgroundColor = UIColor.darkGrayColor()
villainButton.setTitle("Button for Villain: \(villain)", forState: UIControlState.Normal) // We are going to use the item name as the Button Title here.
villainButton.titleLabel?.text = "\(villain)"
villainButton.addTarget(self, action: "villainButtonPressed:", forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(villainButton) // myView in this case is the view you want these buttons added
}
}
func villainButtonPressed(sender:UIButton!) {
if sender.titleLabel?.text != nil {
println("You have chosen Villain: \(sender.titleLabel?.text)")
} else {
println("Nowhere to go :/")
}
}
}
So how it is possible to set and get the tag for/from each button in code (in this example)?
Thank you in advance!
You can use enumerated array to access indexes of the current item in iteration and use that as a tag:
override func viewDidLoad() {
super.viewDidLoad()
var arrayOfVillains = ["santa", "bugs", "superman", "batman"]
var buttonY: CGFloat = 20
for (index, villain) in arrayOfVillains.enumerated() {
let villainButton = UIButton(frame: CGRect(x: 50, y: buttonY, width: 250, height: 30))
buttonY = buttonY + 50 // we are going to space these UIButtons 50px apart
// set the tag to current index
villainButton.tag = index
villainButton.layer.cornerRadius = 10 // get some fancy pantsy rounding
villainButton.backgroundColor = UIColor.darkGrayColor()
villainButton.setTitle("Button for Villain: \(villain)", forState: UIControlState.Normal) // We are going to use the item name as the Button Title here.
villainButton.titleLabel?.text = "\(villain)"
villainButton.addTarget(self, action: "villainButtonPressed:", forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(villainButton) // myView in this case is the view you want these buttons added
}
}
}
#objc func villainButtonPressed(sender:UIButton!) {
// tag of pressed button
print(">>> you pressed button with tag: \(sender.tag)")
if sender.titleLabel?.text != nil {
println("You have chosen Villain: \(sender.titleLabel?.text)")
} else {
println("Nowhere to go :/")
}
}
You should use an integer-based for loop for this.
for i in 0 ..< arrayOfVillains.count {
let villain = arrayOfVillains[i]
let villainButton = UIButton(frame: CGRect(x: 50, y: buttonY, width: 250, height: 30))
villainButton.tag = i
}
You can do as follows,
var buttonTag = 1
for villain in arrayOfVillains {
let villainButton = UIButton(frame: CGRect(x: 50, y: buttonY, width: 250, height: 30))
villainButton.tag = buttonTag
buttonTag += 1
}

Create a variadic method in Swift

I want to make a variadic method to accept any number of textfields and a UIColor as inputs and my intention is to add a bottom line of that specific color to all thus textfields inside method.
How can I make such a method in Swift?
Probably something like this would do:
func applyColour(toTextfields textfields: UITextField..., colour: UIColor) {
for textfield in textfields {
// Apply the colour here.
}
}
Common.addBottonLine(color: UIColor.white, userNameTextField, passwordTextField)
Method definition
class func addBottonLine(color: UIColor, _ txtFields: UITextField... ) -> Void {
for textF in txtFields {
var bottomLine = CALayer()
bottomLine.frame = CGRect.init(origin: CGPoint.init(x: 0, y: textF.frame.height - 2) , size: CGSize.init(width: textF.frame.width, height: 2 ))
bottomLine.backgroundColor = color.cgColor
textF.borderStyle = .none
textF.layer.addSublayer(bottomLine)
}
}
Use this function to add bottom line
func addBottomLabel(_ color: UIColor) {
let lbl1 = UILabel()
lbl1.backgroundColor = color
addSubview(lbl1)
addVisualConstraints(["H:|[label]|", "V:[label(1)]|"], forSubviews: ["label": lbl1])
}
Use :
let textFields = [your textfield array]
for textFields in textFields
{
textFields.addBottomLabel(.yourcolor)
}

Swift: Unrecognized selector sent to class, buttons target in NSObject class

pls do not mark this question as duplicate as I have tried the various solutions but doesn't quite work. I am creating a headerView in my FormViewController (I'm using Eureka) and I separated my views into a separate HeaderViews: NSObject to observe the MVC principle. I attempted to add a buttonTarget but it throws the error unrecognized selector sent to class. My code as follows:
class ProfileSettingsViewController: FormViewController {
override func viewDidLoad() {
super.viewDidLoad()
form +++
Section(){ section in
section.header = {
var header = HeaderFooterView<UIView>(.callback({
let view = HeaderViews.setUpHeaderForProfileView(user: self.user)
return view
}))
header.height = { 200 }
return header
}()
}
}
And in my HeaderViews:
class HeaderViews: NSObject {
static func setUpHeaderForProfileView(user: User) -> UIView {
let view = UIView(frame: CGRect(x: 0, y: 0, width: screen.width, height: 100))
let changeAvatarButton = UIButton(frame: CGRect(x: (screen.width / 2) - 50, y: avatarImageView.frame.maxY + 10, width: 100, height: 20))
changeAvatarButton.addTarget(self, action: #selector(getAvatar(_:)), for: .touchUpInside)
view.addSubview(changeAvatarButton)
return view
}
func getAvatar(_ sender: UIButton!) {
print("Change Avatar tapped")
}
}
I'm not quite sure if I have made any mistake, as I have tried various methods of the selectors in this post: Unrecognized selector sent to class
I suspect it might something to do with the subclass NSObject. Any advice pls?
Update
If you could not get instance of HeaderViews. then just change getAvatar to static funciton.
static func getAvatar(_ sender: UIButton!) {
print("Change Avatar tapped")
}
Origin
You should not use self as target in static function.
Because this self means HeaderViews.
Passing instance to function as target.
Ex:
static func setUpHeaderForProfileView(user: User, in instance: HeaderViews) -> UIView {
let view = UIView(frame: CGRect(x: 0, y: 0, width: screen.width, height: 100))
let changeAvatarButton = UIButton(frame: CGRect(x: (screen.width / 2) - 50, y: avatarImageView.frame.maxY + 10, width: 100, height: 20))
changeAvatarButton.addTarget(instance, action: #selector(getAvatar(_:)), for: .touchUpInside)
view.addSubview(changeAvatarButton)
return view
}
If you are not using the UIButton Parameter, define func getAvatar() with no parameters or func getAvatar(_ sender: AnyObject?)
The function call would change to this.
changeAvatarButton.addTarget(self, action: #selector(getAvatar), for: .touchUpInside)
Also I am curious why not define HeaderViews as a subclass of UIView?
That might actually be the problem here.

How to show image when textfield is empty only?

i have 4 textfields, a button and an image, what i'm trying to achieve is that when the button is pressed it should warn the user there are empty textfields and show the image pointing to the empty textfield(s), but make the image disappear when the user put text into the empty textfield. Thank you
here is my func
func checkFields(){
let userEmail = user_EmailTxtField.text!
let userPassword = user_PassTxtField.text!
let passwordConfirm = pass_ConfirmTxtField.text!
let userPhone = user_PhoneTxtField.text!
//Set backArrow image to show input error to password
let imageName = "backArrow.png"
let image = UIImage(named: imageName)
let passImageView1 = UIImageView(image: image!)
passImageView1.frame = CGRect(x: 319, y: 331, width: 49, height: 49)
view.addSubview(passImageView1)
//Show Arrow image to password confirm error
let passConfirmImageView1 = UIImageView(image: image!)
passConfirmImageView1.frame = CGRect(x: 319, y: 394, width: 49, height: 49)
view.addSubview(passConfirmImageView1)
//Show arrow Image to email error
let emailImageView1 = UIImageView(image: image!)
emailImageView1.frame = CGRect(x: 319, y: 270, width: 49, height: 49)
view.addSubview(emailImageView1)
//Set image to phonetxt field error
let phoneImageView1 = UIImageView(image: image!)
phoneImageView1.frame = CGRect(x: 319, y: 209, width: 49, height: 49)
view.addSubview(phoneImageView1)
if userPhone.isEmpty {
phoneImageView1.isHidden = false
} else {
phoneImageView1.isHidden = true
}
if userEmail.isEmpty {
emailImageView1.isHidden = false
}
if userPassword.isEmpty {
passImageView1.isHidden = false
}
if passwordConfirm.isEmpty {
passConfirmImageView1.isHidden = false
}
}
You can call a function where you handle your check and then hide your image if the text isn't empty like this:
1 Add a target to your textFields at the place where you create them for the event .editingChanged:
user_EmailTxtField.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
user_PhoneTxtField.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
pass_ConfirmTxtField.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
pass_ConfirmTxtField.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
2 Setup the method you use to check if the textfields getting edited:
func textFieldDidChange() {
emailImageView1.isHidden = user_EmailTxtField.hasText
phoneImageView1.isHidden = user_PhoneTxtField.hasText
passImageView1.isHidden = pass_ConfirmTxtField.hasText
passConfirmImageView1.isHidden = pass_ConfirmTxtField.hasText
}
So if you apply this example to all your text fields you don't need the checks in checkFields() and you can rename your the method to setupTextFields() for example to use it only as creation of the text fields
Example to use your image views in all methods over the class, but be sure to initialize in viewDidLoad() for example to avoid found nil errors while unwrapping an optional value
class YourClass: UIViewController {
// ImageView Reference
var emailImageView1: UIImageView!
// then your checkFields method
func checkFields() {
// code before
emailImageView1 = UIImageView(image: image!)
// code after
}
}
you should separate the creation of the image (addSubview) and the check
put the creation to viewDidLoad
and later check the fields and hide it or show it.
just now you create each time a new image which is placed over the previous created images (you can check this if you switch to a wireframe 3d debug view)
Instead of checking if userPhone.isEmpty, you should try:
if user_EmailTxtField.text == nil {
phoneImageView1.isHidden = false
}
You could even do:
if user_EmailTxtField.text == nil || user_EmailTxtField.text == "" {
phoneImageView1.isHidden = false
}

Custom Class view and selector reference

I'm making a custom class that just has some basic functions I can reuse once the class is initialized in any viewController. The problem is I don't know how to refer to the view or any functions being called in the #selector and I get the error: use of unresolved identifier "view". Anyone know how that's done? The sample below is to create a new UIButton.
import Foundation
import UIKit
class Utils {
var newUpdatesBtn: UIButton!
func setupButtonNewUpdates() {
newUpdatesBtn = UIButton(type: UIButtonType.System) // .System
newUpdatesBtn.setTitle("New Updates", forState: UIControlState.Normal)
newUpdatesBtn.bounds = CGRect(x: 0, y: 0, width: 123, height: 27)
newUpdatesBtn.center = CGPoint(x: view.bounds.width / 2, y: 90) // 80 point button, 20 point status bar, 40 point half button, 8 point margins
newUpdatesBtn.setBackgroundImage(UIImage(named: "new-updates"), forState: UIControlState.Normal)
newUpdatesBtn.titleLabel?.font = UIFont.systemFontOfSize(14)
newUpdatesBtn.setTitleColor(UIColor.whiteColor(), forState: UIControlState.Normal)
newUpdatesBtn.addTarget(self, action: #selector(newUpdatesButtonPressed), forControlEvents: UIControlEvents.TouchUpInside)
self.newUpdatesBtn.alpha = 0;
self.navigationController!.view.addSubview(newUpdatesBtn)
UIView.animateWithDuration(0.3, animations: { () -> Void in
self.newUpdatesBtn.alpha = 1;
})
}
}
(problem lines)
view.bounds.width
newUpdatesBtn.addTarget(self, action: #selector(newUpdatesButtonPressed), forControlEvents: UIControlEvents.TouchUpInside)
This is because you have not set any reference in view property.
You can do like this:
func setupButtonNewUpdates(targetView:UIView)
{
newUpdatesBtn = UIButton(type: UIButtonType.System) // .System
.
.
.
newUpdatesBtn.center = CGPoint(x: targetView.bounds.width / 2, y: 90) // 80 point button, 20 point status bar, 40 point half button, 8 point margins
.
.
.
targetView.addSubView(newUpdatesBtn);
}
Now whenever you call this method from any viewcontroller just pass the view of that VC. for example:
var util:Utils = new Utils();
util.setupButtonNewUpdates(self.view);

Resources