Swift - UIButton created programatically doesn't trigger touch event - ios

I have created a button programatically and want to add a touch event handler. I have tried all the tricks I have found online but the desired function is just never triggered.
upgradeNowView = UIButton()
upgradeNowView.setTitle("Upgrade", forState: .Normal)
upgradeNowView.contentMode = UIViewContentMode.ScaleToFill
upgradeNowView.frame = CGRect(x: upgradeNowView.frame.minX, y: -50 , width: 320, height: 50)
upgradeNowView.userInteractionEnabled = true
upgradeNowView.sendActionsForControlEvents(.TouchUpInside)
upgradeNowView.addTarget(self, action: #selector(MainViewContainer.upgradeTapped(_:)), forControlEvents: .TouchUpInside)
side_menu_controller?.view.addSubview(upgradeNowView)
and defined this
func upgradeTapped(sender: UIButton)
{
UpgradeViewController.openUpgrade()
}
But this just never gets called. Any help would be appreciated.

You need to define upgradeTapped function in class of side_menu_controller instance. I guess, it's SideMenuController class.

I found out this was caused by the fact that I was placing the button next to the view but outside its bounds (just on top):
upgradeNowView.frame = CGRect(x: upgradeNowView.frame.minX, y: -50 , width: 320, height: 50)
There are two ways around this:
Move the button inside the bounds of the parent view, or
Handle touches outside the view by overriding "hitTest" as discussed here

Related

How to add Material Design Button in iOS swift app?

I have read the manual (https://material.io/develop/ios/components/buttons/), but still have not understood how to do it.
class FloatingButtonController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let floatingButton = MDCFloatingButton()
floatingButton.setImage( UIImage(named: "plus"), for: .normal)
floatingButton.backgroundColor = .white
floatingButton.setElevation(ShadowElevation(rawValue: 6), for: .normal)
floatingButton.addTarget(self, action: #selector(btnFloatingButtonTapped(floatingButton:)), for: .touchUpInside)
self.view.addSubview(floatingButton)
}
#objc func btnFloatingButtonTapped(floatingButton: MDCFloatingButton){
floatingButton.collapse(true) {
floatingButton.expand(true, completion: nil)
}
}
}
My project on the screen. As you can see - the button is missing on the screen. When you click on a blank screen errors appear.
Button touch target does not meet minimum size guidlines of (48, 48). Button: >, Touch Target: {0, 0}
Screenshot of my project
Tell me, please, what am I doing wrong? And how do I get the job right?
You didn't set the frame to button. You need to specify (x, y) position to place button in view.
floatingButton.frame = CGRect(x: 300, y: 300, width: 48, height: 48)
To add button target the button should have size atlease 48x48.
You could constraint it manually or set frame.
OR you could add button via Storyboard too.

Is there a different option than addTarget on a UIButton

addTarget
So I know that you can use addTarget on a button
like so:
import UIKit
class ViewController: UIViewController {
func calledMethod(_ sender: UIButton!) {
print("Clicked")
}
override func viewDidLoad() {
super.viewDidLoad()
let btn = UIButton(frame: CGRect(x: 30, y: 30, width: 60, height: 30))
btn.backgroundColor = UIColor.darkGray
btn.addTarget(self, action: #selector(self.calledMethod(_:)), for: UIControlEvents.touchUpInside)
self.view.addSubview(btn)
}
}
This works absolutely fine but I was just wondering is there another way to detect when something is clicked and run some code when that happens.
You can add button in the storyboard and create an outlet for it's action.
#IBAction func buttonAction(_ sender: UIButton) {
//implement your code here
}
A button is a subclass of UIControl. A UIControl is designed to use target/action to specify code to run when the specified action occurs. If you don't want to use that mechanism, you have to create an equivalent.
You could attach a tap gesture recognizer to some other object and set it's userInteractionEnabled flag to true, but gesture recognizers ALSO use the target/action pattern.
My suggestion is to "stop worrying and learn to love the button." Just learn to use target/action.
Another possibility: Create a custom subclass of UIButton that sets itself up as it's own target at init time (specifically in init(coder:) and init(frame:), the 2 init methods you need to implement for view objects.) This button would include an array of closures that its action method would execute if the button was tapped. It would have methods to add or remove closures. You'd then put such a button in your view controller and call the method to add the closure you want.
You can also have a look at RxSwift/RxCocoa or similar. All the changes and actions are added automatically and you can decide if you want to observe them or not.
The code would look something like this:
let btn = UIButton(frame: CGRect(x: 30, y: 30, width: 60, height: 30))
btn.backgroundColor = UIColor.darkGray
btn
.rx
.tap
.subscribe(onNext: {
print("Clicked")
}).disposed(by: disposeBag)
view.addSubview(btn)

Class function called from another class

I have an Objective-C method like this:
-(void)findFiles{
}
It is working fine.But I need to call this method in Swift class.I have done :
var instanceOfCustomObject:DriveListTableViewController = DriveListTableViewController()
and now I can use the method like this:
instanceOfCustomObject.find()
But it works only in if I place it into viewDidLoad.
Is there a way that I can call it on click of button?
I have created button
let button:UIButton = UIButton(frame: CGRect(x: 100, y: 400, width: 100, height: 50))
button.backgroundColor = .black
button.setTitle("Button", for: .normal)
button.addTarget(self, action:#selector(self.buttonClicked), for: .touchUpInside)
self.view.addSubview(button)
func buttonClicked() {
print("Button Clicked")
instanceOfCustomObject.findFiles()
}
But it still works only if I place it into viewDidLoad. Please help me! Is there any other way to make it call in Swift file from Objective-C file??
Declare your var instanceOfCustomObject outside the viewDidLoad method (i.e., inside the class but outside the method).

Add a dynamic part as subview in a view controller

I am trying to make a reusable PopUp view controller composed of the following:
a static part (the top one) with an image and a label
a dynamic part (lower one) that can contain either 1 large button, or 2 smaller one side by side or 2 smaller buttons on top of one another.
It should look like this:
For now here is what I did:
Created a PopUpViewController class
In the storyboard, added a VC with the PopUpViewController class
Added the outlets for the static part
And now, I don't know how to proceed.
The idea of showing/hiding objects depending on the style of the popup (1 or 2 buttons etc.) seems horrible to me.
I don't think I can use xibs ?
I wish I could use storyboards because I am unable to do the auto layout via code.
Is code still the way to go ?
You can create 3 buttons inside your code. And using an if statement either show the 2 small buttons or the big one. Otherwise i think u can add 2 "View" objects in your storyboard and add 2 buttons in one and 1 button on the other and show and hide the "View" you want again with an if statement.
Here is an example :
import UIKit
class ViewController: UIViewController {
var flag = false
var buttonSmallOne: UIButton!
var buttonSmallTwo: UIButton!
var buttonBigOne: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Create the 3 buttons
buttonSmallOne = UIButton(frame: CGRect(x: self.view.bounds.width/2 - 50, y: self.view.bounds.height/2, width: 50, height: 50))
buttonSmallOne.backgroundColor = .green()
buttonSmallOne.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
buttonSmallTwo = UIButton(frame: CGRect(x: self.view.bounds.width/2 + 50, y: self.view.bounds.height/2, width: 50, height: 50))
buttonSmallTwo.backgroundColor = .green()
buttonSmallTwo.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
self.view.addSubview(buttonSmallOne)
self.view.addSubview(buttonSmallTwo)
buttonBigOne = UIButton(frame: CGRect(x: self.view.bounds.width/2-100, y: self.view.bounds.height/2, width: 100, height: 50))
buttonBigOne.backgroundColor = .red()
buttonBigOne.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
self.view.addSubview(buttonBigOne)
buttonBigOne.isHidden = true
buttonBigOne.isEnabled = false
}
func buttonAction(sender: UIButton!) {
if flag == false {
// Hide
buttonSmallOne.isHidden = true
buttonSmallOne.isEnabled = false
buttonSmallTwo.isHidden = true
buttonSmallTwo.isEnabled = false
// Show
buttonBigOne.isHidden = false
buttonBigOne.isEnabled = true
flag = true
}else{
// Show
buttonSmallOne.isHidden = false
buttonSmallOne.isEnabled = true
buttonSmallTwo.isHidden = false
buttonSmallTwo.isEnabled = true
// Hide
buttonBigOne.isHidden = true
buttonBigOne.isEnabled = false
flag = false
}
}
}
I would advise you to create a class for the container (for the dynamic content).
Create one class with a stack view in it(you can change from horizontal to vertical, based on button logic ).
Create third class with button and label in it.
Construct the container with the right views based on Enum configuration.
Create everything from code and added this container subview to your popOver on creation.
If you have to support iOS 8 you can use this with reachability(check for ios version). It works great for this kind of problems. And later on you can just rename it and delete iOS 8 code from the project. It has the same API as a stackView.
https://github.com/tomvanzummeren/TZStackView

How to remove multiple buttons that I created with a loop?

EXPLANATION:
1) I initiate the button above the class :
var myButton = UIButton()
2) I then create x amount of buttons depending on how many items are in an array
for letter in arrayOfLetters {
myButton = UIButton(frame: CGRect(x: buttonX, y: 500, width: someFloat, height: someFloat))
buttonX = buttonX + thirdFloat //spacing
myButton.layer.cornerRadius = 5
myButton.backgroundColor = UIColor.darkGrayColor()
myButton.setTitle("\(letter)", forState: UIControlState.Normal)
myButton.titleLabel?.text = "\(letter)"
myButton.addTarget(self, action: "myButtonPressed:", forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(myButton)
}
3) Later on once the user has pressed the correct buttons, I call a function to refresh and I want to remove all buttons. This is so I can generate new buttons based on new array
myButton.removeFromSuperview()
ISSUE:
However this does nothing, I tried giving the buttons a tag and deleting buttons by tag == tagId but nothing happened.
I can delete all views but it deletes everything else,
I tried this but again nothing happends
var buttons = [myButton]
for button in buttons as! [UIButton] {
button.removeFromSuperview()
}
I want to delete all buttons I added and i'm either not deleting them or deleting everything in the view
EASY FIX:
1) Inside Class, out of any func:
var buttonsArray = [UIButton]()
2) Declare myButton here and add the append line:
for letter in arrayOfLetters {
var myButton = UIButton(frame: CGRect(x: buttonX, y: 500, width: someFloat, height: someFloat))
...
self.buttonsArray.append(myButton)
}
3) To remove buttons:
for btn in buttonsArray {
btn.removeFromSuperview()
}
EXPLANATION:
Always store in an array the elements you create with a loop if you want to easily remove all of them at once.
i hope it will help for you give tag for your button while you are adding delete that button
for button in self.view.subviews {
if button.tag == 100 {
button.removeFromSuperview()
}
}
you are re-assigning the myButton variable as you loop.
Remove this line:
var myButton = UIButton()
add a property at the top to hold the buttons:
var buttons:[UIButton]()
then later in your code:
for letter in arrayOfLetters {
var myButton = UIButton(frame: CGRect(x: buttonX, y: 500, width: someFloat, height: someFloat))
buttonX = buttonX + thirdFloat //spacing
myButton.layer.cornerRadius = 5
myButton.backgroundColor = UIColor.darkGrayColor()
myButton.setTitle("\(letter)", forState: UIControlState.Normal)
myButton.titleLabel?.text = "\(letter)"
myButton.addTarget(self, action: "myButtonPressed:", forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(myButton)
self.buttons.append(myButton)
}
Then you should be able to remove like so:
for button in self.buttons {
button.removeFromSuperview()
}
This should resolve your issue, But it's important to understand what the problem was, so you define a new button as a property with this line: var myButton = UIButton(). This holds ONE button. as you are looping through creating buttons you are overriding the button with the next one.
So if anything, Calling myButton.removeFromSuperView() will remove the last one you created. Doing it the way I suggested means you create a button and add it to an array of buttons, so later you can iterate over the array referencing each button in turn and remove it.

Resources