UIButton selector not work - ios

I can't call buttonAction method, when i click nothing happen. I used the last #selector of swift 2.2 and my buttonAction function is already outside notificationAlert function.
class AlertHelper: UIViewController {
var cancel_button: UIButton!
func notificationAlert(message:String, viewController : UIViewController, color: UIColor) {
cancel_button = UIButton(
frame: CGRect(
x: viewController.view.center.x,
y: viewController.view.center.y + 50,
width: 100,
height: 50
)
)
//Bind Click on Button ERROR
cancel_button.addTarget(self, action: #selector(AlertHelper.buttonAction(_:)), forControlEvents: UIControlEvents.TouchUpInside)
viewController.view.addSubview(cancel_button)
}
class func displayError(message:String, viewController : UIViewController) {
AlertHelper().notificationAlert(message, viewController: viewController, color : .whiteColor())
}
func buttonAction(sender: UIButton!)
{
print("ok")
}
}

Change your AlertHelper class as follows and make that a singleton class
class AlertHelper: UIViewController {
var cancel_button: UIButton!
// Here is your static object that will always remain in memory
static let sharedInstance = AlertHelper()
func notificationAlert(message:String, viewController : UIViewController, color: UIColor) {
cancel_button = UIButton(
frame: CGRect(
x: viewController.view.center.x,
y: viewController.view.center.y + 50,
width: 100,
height: 50
)
)
//Bind Click on Button ERROR
cancel_button.addTarget(self, action: #selector(AlertHelper.buttonAction(_:)), forControlEvents: UIControlEvents.TouchUpInside)
viewController.view.addSubview(cancel_button)
}
func displayError(message:String, viewController : UIViewController) {
//Maintaining the same static object and not making any new object
AlertHelper.sharedInstance.notificationAlert(message, viewController: viewController, color : .whiteColor())
}
func buttonAction(sender: UIButton!) {
print("ok")
}
}
Now in your other controller you can all as follows:
AlertHelper.sharedInstance.displayError("Check", viewController: self)
And you are done. Click will work!

Related

How to add an action to a UIButton that is in another class

The code below compiles fine, but crashes with an unrecognized selector sent to instance error.
I have one class that inherits from UIViewController:
class Controller: UIViewController {
override func viewDidLoad() {
let toolbarWrapper = CustomToolbarWrapper(view: view, target: self)
let toolbar = toolbarWrapper.toolbarView
view.addSubview(toolbar)
... Other code ...
}
}
And another class that is just a wrapper for a UIView and contains buttons:
class CustomToolbarWrapper {
var toolbarView: UIView
init(view: UIView, target: Any) {
let height: CGFloat = 80
toolbarView = UIView(frame: CGRect(x: 0, y: view.frame.height - height, width: view.frame.width, height: height))
let button = UIButton()
... Some button layout code ...
button.addTarget(target, action: #selector(CustomToolbar.buttonTapped(_:)), for: .touchUpInside)
toolbarView.addSubview(button)
}
#objc static func buttonTapped(_ sender: Any) {
print("button tapped")
}
}
For the sake of clarity, I left out a large chunk of code and kept what I thought was necessary. I think that my code doesn't work because of my misunderstanding of the how the target works in the addTarget function. Normally, I would just use self as the target of my button's action, so I just tried to pass along self from the view controller to the CustomToolbarWrapper's init function.
What else I have tried:
Changing the button's target from target to self like this:
button.addTarget(self, action: #selector(CustomToolbar.buttonTapped(_:)), for: .touchUpInside)
results in the app not crashing anymore. Instead, however, I believe that line of code fails to do anything (which doesn't throw an error for some reason?) because attempting to print button.allTargets or even button.allTargets.count results in the app crashing at compile time, with an EXC_BREAKPOINT error and no error description in the console or the XCode UI (which just confuses me even more because there are no breakpoints in my code!).
Also, making buttonPressed(_:) non-static does not change any of the previously mentioned observations.
Also, to make sure the button could in fact be interacted with, I added this in the viewDidLoad() of Controller:
for subview in toolbar.subviews? {
if let button = subview as? UIButton {
button.addTarget(self, action: #selector(buttonPressed(_:)), for: .touchUpInside)
}
}
and added a simple testing method to Controller for the button:
#objc func buttonPressed(_ sender: UIButton) {
print("Button Pressed")
}
And running the code did result in "Button Pressed" being printed in the console log, so the button should be able to be interacted with by the user.
Feel free to let me know if you think this is not enough code to figure out the problem, and I will post more details.
Edit
I prefer to keep the implementation of the button's action in the CustomToolbarWrapper class to prevent repeating code in the future, since the action will be the same no matter where an instance of CustomToolbarWrapper is created.
The best option would be to add the target in your controller and then call a method in your toolbarWrapper on button press. But if you really need to keep this design, you should have a strong reference to your toolbarWrapper in your controller class, otherwise your toolbarWrapper is deallocated and nothing gets called. Also, the buttonTapped(_:) method does not need to be static. Thus, in your controller:
class Controller: UIViewController {
var toolbarWrapper: CustomToolbarWrapper?
override func viewDidLoad() {
toolbarWrapper = CustomToolbarWrapper(view: view, target: self)
let toolbar = toolbarWrapper.toolbarView
view.addSubview(toolbar)
... Other code ...
}
}
And in your wrapper:
class CustomToolbarWrapper {
var toolbarView: UIView
init(view: UIView, target: Any) {
let height: CGFloat = 80
toolbarView = UIView(frame: CGRect(x: 0, y: view.frame.height - height,width: view.frame.width, height: height))
let button = UIButton()
... Some button layout code ...
button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
toolbarView.addSubview(button)
}
#objc func buttonTapped(_ sender: Any) {
print("button tapped")
}
}
There is another way I would use which is delegation. The target does not necessarily have to be a controller, it can be the CustomToolbarWrapper itself.
First, declare a protocol
protocol CTDelegate: AnyObject {
func didClickButton()
}
Then in CustomToolbarWrapper add a property, weak var delegate: CTDelegate? and a button action:
#objc func buttonTapped(_ sender: UIButton) {
delegate?.didClickButton()
}
So in your case, it becomes:
button.addTarget(self, action: #selector(CustomToolbarWrapper.buttonTapped(_:)), for: .touchUpInside)
Then when you go to any ViewController, conform to CTDelegate and initialize the CustomToolbarWrapper, you can set its delegate to the controller.
e.g
let toolbarWrapper = CustomToolbarWrapper(view: view, target: self)
toolbarWrapper.delegate = self
and implement your action inside the method you are conforming to in your controller i.e.
func didClickButton()
Your problem is right here:
let toolbarWrapper = CustomToolbarWrapper(view: view, target: self)
You're passing an instance of Controller class which doesn't implement the buttonTapped(_:) selector. It is implemented by your CustomToolbarWrapper class. This is a bad design in general. You should either follow a delegate pattern, or a callback pattern.
Updated Answer:
Delegate pattern solution:
class Controller: UIViewController, CustomToolbarWrapperDelegate {
override func viewDidLoad() {
let toolbarWrapper = CustomToolbarWrapper(view: view, buttonDelegate: self)
let toolbar = toolbarWrapper.toolbarView
view.addSubview(toolbar)
}
// MARK: - CustomToolbarWrapperDelegate
func buttonTapped(inToolbar toolbar: CustomToolbarWrapper) {
print("button tapped")
}
}
protocol CustomToolbarWrapperDelegate: AnyObject {
func buttonTapped(inToolbar toolbar: CustomToolbarWrapper) -> Void
}
class CustomToolbarWrapper {
var toolbarView: UIView
weak var buttonDelegate: CustomToolbarWrapperDelegate?
init(view: UIView, buttonDelegate: CustomToolbarWrapperDelegate?) {
let height: CGFloat = 80
toolbarView = UIView(frame: CGRect(x: 0, y: view.frame.height - height, width: view.frame.width, height: height))
self.buttonDelegate = buttonDelegate
let button = UIButton()
button.addTarget(self, action: #selector(self.buttonTapped(_:)), for: .touchUpInside)
toolbarView.addSubview(button)
}
#objc private func buttonTapped(_ sender: Any) {
// Your button's logic here. Then call the delegate:
self.buttonDelegate?.buttonTapped(inToolbar: self)
}
}
If you'd rather stick to your current design then just implement the following changes:
class Controller: UIViewController {
override func viewDidLoad() {
let toolbarWrapper = CustomToolbarWrapper(view: view, target: self, selector: #selector(self.buttonTapped(_:)), events: .touchUpInside)
let toolbar = toolbarWrapper.toolbarView
view.addSubview(toolbar)
}
#objc private func buttonTapped(_ sender: Any) {
print("button tapped")
}
}
class CustomToolbarWrapper {
var toolbarView: UIView
init(view: UIView, target: Any?, selector: Selector, events: UIControlEvents) {
let height: CGFloat = 80
toolbarView = UIView(frame: CGRect(x: 0, y: view.frame.height - height, width: view.frame.width, height: height))
let button = UIButton()
button.addTarget(target, action: selector, for: events)
toolbarView.addSubview(button)
}
}

Swift 4 UIButton action in UIImageView not working

I am trying to add a delete button as a subview in an image. This is my current structure:
-> class DesignViewController: UIViewController
|
-> class Sticker: UIImageView, UIGestureRecognizerDelegate
|
-> UI button inside the Sticker
Inside Sticker class I have :
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let button2 = UIButton(frame: CGRect(x: 100, y: 100, width: 100, height: 50))
button2.backgroundColor = .red
button2.setTitle("Delete", for: .normal)
button2.tag = 23
button2.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
self.addSubview(button2)
}
#objc func buttonAction(sender: UIButton!) {
print("Button tapped")
}
The buttonAction is not getting called.
When I change self.addSubview(button2) line to :
self.superview?.addSubview(button2)
I can see buttonAction getting called. However I would like to keep the button inside the Sticker view so that when user moves the sticker, the button moves as a subview with it.
Can anyone please help and let me know how I can keep the button inside Sticker view?
By default isUserInteractionEnabled property of UIImageView is set to false. Set it to true and your button will start to respond. You can set it in code as well as in the storyboards.
Also try setting the clipsToBounds property of your imageview to true. It will clip your button if it is going outside of the image bounds. That might be one of the reason that your button is not getting touches.
You should create a protocol delegate for button action. This is code example:
protocol ButtonDelegate: class {
func buttonTapped(button: UIButton)
}
class Sticker: UIImageView {
weak var delegate: ButtonDelegate?
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(button2)
}
lazy var button2: UIButton = {
let button = UIButton(frame: CGRect(x: 100, y: 100, width: 100, height: 50))
button2.backgroundColor = .red
button2.setTitle("Delete", for: .normal)
button2.tag = 23
button2.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
return button
}()
#objc func buttonAction(sender: UIButton) {
guard let delegate = delegate else { return }
delegate.buttonTapped(button: sender)
}
So, now go to your DesignViewControllerl, add your custom imageview class Sticker. Don't forget to do that "imageView.delegate = self". Then in extension add protocol delegate you've created before. Code example:
class DesignViewController: UIViewController {
private lazy var sticker: Sticker = {
let iv = Sticker(frame: view.bounds)
iv.delegate = self
return iv
}()
override viewDidLoad() {
super.viewDidLoad()
view.addSubiew(sticker)
}
}
extension DesignViewController: ButtonDelegate {
func buttonTapped(button: UIButton) {
// input your action here
}
}

Master view not updating with delegate from detail view

I am trying to update the Master view in a UISplitView when a user taps a UIBarButtonItem in the detail view, which represents a favourite button. For some reason, the delegate method never gets called. Relevant code below. Any suggestions?
import UIKit
import WebKit
protocol FavouriteCaseDelegate: class {
func updateMasterForFavouriteStatusChange(caseID: Int, favouriteStatus: Bool)
}
class DetailViewController: UIViewController, UITableViewDelegate, UITableViewDataSource
weak var favouriteDelegate: FavouriteCaseDelegate? = nil
override func viewDidLoad() {
super.viewDidLoad()
//Configure favourite bar button
favouriteButton = UIButton.init(type: .custom)
if caseFavourited == false {
favouriteButton.setImage(UIImage(named: "FavouriteIcon"), for: UIControlState.normal)
} else {
favouriteButton.setImage(UIImage(named: "FavouriteIconSelected"), for: UIControlState.normal)
}
favouriteButton.addTarget(self, action:#selector(didTapFavouriteBarButton), for: UIControlEvents.touchUpInside)
favouriteButton.frame = CGRect.init(x: 0, y: 0, width: 30, height: 30)
let favouriteBarButton = UIBarButtonItem.init(customView: favouriteButton)
self.navigationItem.rightBarButtonItem = favouriteBarButton
}
func didTapFavouriteBarButton() {
caseFavourited = !caseFavourited
CaseManager.caseWorker.changeFavouritedStateForCase(subjectID: subjectID, caseID: caseID, isFavourited: caseFavourited)
favouriteDelegate?.updateMasterForFavouriteStatusChange(caseID: caseID, favouriteStatus: caseFavourited)
}
import UIKit
class MasterTableViewController: UITableViewController, FavouriteCaseDelegate {
func updateMasterForFavouriteStatusChange(caseID: Int, favouriteStatus: Bool) {
print("updateMasterForFavourite called")
self.tableView.reloadData()
}
}
Inside your MasterTableViewController class you need to set DetailViewController-> favouriteDelegate to self for instance:-
//Assuming you have reference of detailViewController inside your MasterTableViewController class
let detailViewController = DetailViewController()
detailViewController.favouriteDelegate = self
Once set the delegate and invoke the method then it should called.

UIButton calls function from wrong class

I have a ButtonProvider class that creates my button and adds a target action. However, when the button is pressed, it doesn't call the function from the ButtonProvider class in which it was created, but instead calls a function with the same name from the other class.
class ButtonProvider {
let view: UIView!
let button: UIButton!
init(view: UIView) {
self.view = view
}
func showButton() {
button = UIButton(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
button.backgroundColor = .white
button.addTarget(self, action: #selector(ButtonProvider.close), for: .touchUpInside)
view.addSubview(button)
}
#objc func close() {
button.removeFromSuperview()
}
}
And in my calling ViewController:
override func viewDidLoad() {
let buttonProvider = ButtonProvider(view: self.view)
buttonProvider.showButton()
}
#objc func close() {
//This is the function that is called
}
Any ideas why a selector of ButtonProvider.close calls my ViewController.close function?
You buttonProvider is not owned by the controller therefore it will be deallocated as soon as viewDidLoad ends. When the button is pressed the instance does not exist in memory any more and the result will be undefined behavior, usually a crash.
You will have to save buttonProvider to a strong property in the controller.

UIButton not clickable when implemented from a class

I have a class ManageCell, which stores the frames, set the text of labels, etc... Which are the sub-views of an UIView CellView which is in the ViewController.
ManageCell:
import Foundation
import UIKit
class ManageCell {
var name: UILabel
var viewBelowButton: UIView
var deleteButton: UIButton
init (name: String) {
self.name = UILabel(frame: CGRectMake(10,15,250,40)
self.name.text = name
self.name.sizeToFit()
self.viewBelowButton = UIButton(frame: CGRectMake(UIScreen.mainScreen.bounds.width, 0, 70, 70)
//set outside the visible area so that it can be animated in.
self.viewBelowButton.backgroundColor = UIColor.redColor()
self.deleteButton = UIButton(frame: CGRectMake(0,0,70,70))
self.deleteButton.addTarget(ViewController.self, action: "deleteButtonPressed:", forControlEvents: UIControlEvents.TouchUpInside)
self.deleteButton.setTitle("delete", forState: .Normal)
}
}
ViewController:
var cellView: [UIView] = []
var manageCells: [ManageCell] = []
...
//fill the manageCells array
func setSubViews () {
for (index, cell) in manageCells.enumerate() {
cellView.append(UIView(frame: CGRectMake(0, originY, view.bounds.width, 70)
cellView[index].addSubview(cell.name)
cellView[index].addSubview(cell.viewBelowButton)
cell.viewBelowButton.addSubview(cell.deleteButton)
}
}
func editing () {
var frame = CGRectMake(view.bound.width - 70, 0, 0, 70)
for cell in cells {
UIView.animateWithDuration(0.2, animations: {
cell.viewBelowButton.frame = frame
}
}
}
func deleteButtonPressed(sender: UIButton) {
print("button pressed")
}
User Interaction is enabled on both cellView[index], viewBelowButton and deleteButton.
The problem I'm facing is that the deleteButton does not respond to touches. The deleteButtonPressed: function is not being called.
code: https://github.com/an23lm/swift-stuff
I'm not sure if this is good practice, any suggestions are welcome.
Of course it's not called, ViewController.self is a class type, not your View Controller. And if even it was, it's not a good practice. You should use a delegate pattern here with some parameter to be returned back, so you will distinguish which cell delete button was pressed.
Example on your code:
protocol ManageCellDelegate: class {
func manageCellDeletePressed(id: Int)
}
class ManageCell {
var name: UILabel
var viewBelowButton: UIView
var deleteButton: UIButton
weak var delegate: ManageCellDelegate?
var id: Int
init (id: Int, name: String) {
self.id = id
self.name = UILabel(frame: CGRectMake(10,15,250,40))
self.name.text = name
self.name.sizeToFit()
self.viewBelowButton = UIButton(frame: CGRectMake(UIScreen.mainScreen().bounds.width, 0, 70, 70))
//set outside the visible area so that it can be animated in.
self.viewBelowButton.backgroundColor = UIColor.redColor()
self.deleteButton = UIButton(frame: CGRectMake(0,0,70,70))
self.deleteButton.addTarget(self, action: "deleteButtonPressed:", forControlEvents: UIControlEvents.TouchUpInside)
self.deleteButton.setTitle("delete", forState: .Normal)
}
func deleteButtonPressed(sender: AnyObject) {
self.delegate?.manageCellDeletePressed(id)
}
}
class ViewController: UIViewController {
var cellView: [UIView] = []
var manageCells: [ManageCell] = []
func fullManageCells() {
for id in 0...15 {
let manageCell = ManageCell(id: id, name: "something")
manageCell.delegate = self
manageCells.append(manageCell)
}
}
}
extension ViewController: ManageCellDelegate {
func manageCellDeletePressed(id: Int) {
println("button with id \(id) pressed")
}
}

Resources