I have a simple view V and view controller C. The controller calls a separate class X that build a webview and a button to close the webview.
I instantiate class X (with a reference to view V) then call a method to attach both items (webview and items).
When i call the button addTarget method, it does not work. I want it to execute the closeAll method of the X class and not the closeAll method of the C controller.
I have tried hundreds of variants.
Here is (parts of) the code in C controller:
let parentView:UIView
...
#objc func closeAll() {
print("Close webview, object")
}
and this in X class:
...
let transparentButton = UIButton(frame: frame)
transparentButton.backgroundColor = UIColor.black.withAlphaComponent(self.overlayTransparency)
transparentButton.setTitle("", for: .normal)
transparentButton.alpha = 0.5
transparentButton.isUserInteractionEnabled = true
transparentButton.addTarget(self, action:#selector("closeAll"), for: .touchUpInside)
parentView.addSubview(transparentButton)
I have this in my C controller and it get called on click:
#objc func closeAll() {
print("Close webview, main")
}
Change self to instance of a class that you want it's method to be triggered
You can insert either of these into your class:
override func viewDidLoad() {
super.viewDidLoad()
transparentButton.addTarget(self, action:#selector(closeAll), for: .UIControlEvents.valueChanged)
}
func closeAll(sender:AnyObject) {
// your code
}
OR
#IBOutlet var btnStartJob: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
btnStartJob.addTarget(self, action: #selector(closeAll), for: .touchUpInside)
}
func closeAll(_ sender : UIButton) {
// your code
}
Related
Is there any way to use addTarget on something other than self (which seems to be the most common use case)?
Yes, you can use a target other than self. The most common use is to call addTarget with self where self is a reference to the viewController that the adds the UIControl to its view hierarchy. But you aren't required to use it that way. The target is merely a reference to an object, so you can pass it a reference to any object you want. The action is a Selector which needs to be defined as an instance method on the class of that object, and that method must be available to Objective-C (marked with #objc or #IBAction) and it must take either no parameters, just the sender, or the sender and the event.
You can also pass nil as the target, which tells iOS to search up the responder chain for the action method.
Here's a little standalone example:
import UIKit
class Foo {
#objc func buttonClicked() {
print("clicked")
}
}
class ViewController: UIViewController {
let foo = Foo()
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton(frame: CGRect(x: 50, y: 200, width: 100, height: 30))
button.setTitle("Press me", for: .normal)
button.setTitleColor(.blue, for: .normal)
button.addTarget(foo, action: #selector(Foo.buttonClicked), for: .touchUpInside)
self.view.addSubview(button)
}
}
You can certainly set up some other object to receive control actions. Consider the following view controller:
First, define a class who's job is to respond to button tap actions:
#objc class ButtonTarget: NSObject {
#IBAction func buttonAction(_ sender: Any) {
print("In \(#function)")
}
}
Now define a view controller that creates a ButtonTarget object
class ViewController: UIViewController {
#IBOutlet weak var button: UIButton!
lazy var buttonTarget = ButtonTarget() //Give the ViewController a `ButtonTarget`
override func viewDidLoad() {
super.viewDidLoad()
//Add a taret/action to the button that invokes the method `buttonAction(_:)`
button.addTarget(
buttonTarget,
action: #selector(ButtonTarget.buttonAction(_:)),
for: .touchUpInside)
}
}
// Funtion Method
func TappedButtonCallFunction()
{
btnSaveFilterWorkLog(UIButton.init())
}
// Button Action
#IBAction func btnSaveFilterWorkLog(_ sender: UIButton) {
print("Button Tapped")
}
// Button Tapped
The proper way is to use sendAction method from the UIControl class.
You call this method when you want the control to perform the actions associated with the specified events.
In our case:
button.sendActions(for: .touchUpOutside)
Just put ? in function declaration behind uibutton object. Now you can pass nil value in argument but make sure you are not using sender anywhere
And do not worry your button tap event will not effected it will work same
//Calling
btnSaveFilterWorkLog(nil)
// Button Action
#IBAction func btnSaveFilterWorkLog(_ sender: UIButton?) {
print("Button Tapped")
}
also you can create programmatically method
for example:
button.addTarget(self, action: #selector(buttonAction), forControlEvents: .TouchUpInside)
and method selector:
#objc
func buttonAction() {
print("some")
}
don't forget to put #objc
import UIKit
class ActionSheetViewController: UIViewController {
#IBOutlet weak var closeButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
closeButton.addTarget(self, action: #selector(dismissActionSheet), for: .touchUpInside)
}
#objc func dismissActionSheet() {
// some code goes here ie:
dismiss(animated: true, completion: nil)
}
}
Above works perfectly in Swift5.
I wrote a class that shall handle UIBarButtonItem taps.
The initializer takes a reference to an UINavigationItem. All buttons etc. are attached to this UINavigationItem. I tried to connect them with actions (didPressMenuItem()), but when I click the button, the action is not triggered (nothing is written to the console nor the breakpoint I set is triggered).
How can I link the UIBarButtonItem to the function defined in this class?
internal final class NavigationBarHandler {
// MARK: Properties
private final var navigationItem: UINavigationItem?
// MARK: Initializers
required init(navigationItem: UINavigationItem?) {
self.navigationItem = navigationItem
}
internal final func setupNavigationBar() {
if let navigationItem = navigationItem {
let menuImage = UIImage(named: "menu")?.withRenderingMode(.alwaysTemplate)
let menuItem = UIBarButtonItem(image: menuImage, style: .plain, target: self, action: #selector(didPressMenuItem(sender:)))
menuItem.tintColor = .white
navigationItem.leftBarButtonItem = menuItem
}
}
#objc func didPressMenuItem(sender: UIBarButtonItem) {
print("pressed")
}
}
This is what happens in the view controller to which navigationItem the buttons etc. are attached.
class ContactsController: UIViewController {
// MARK: View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .red
self.title = "Kontakte"
let navigationBarHandler = NavigationBarHandler(navigationItem: self.navigationItem)
navigationBarHandler.setupNavigationBar()
}
}
Th problem here is that you're instantiating NavigationBarHandler inside viewDidload() which is why the memory reference dies after viewDidLoad() finishes. What you should do is to create the variable outside like this.
class ContactsController: UIViewController {
var navigationBarHandler: NavigationBarHandler!
// MARK: View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .red
self.title = "Kontakte"
self.navigationBarHandler = NavigationBarHandler(navigationItem: self.navigationItem)
navigationBarHandler.setupNavigationBar()
}
}
This way the memory reference stays.
When I tap on the button, I want to bring up the Company Details view controller programmatically (using no storyboard), but I'm getting an NSException error for the button's function. I believe CompanyDetailsViewController() is null and I'm not sure why because I created a class called CompanyDetailsViewController
func detailsButtonTapped(sender: UIButton!) {
let companyDetailsViewController = CompanyDetailsViewController()
self.navigationController?.pushViewController(companyDetailsViewController, animated: true)
}
Company Details View Controller Class
import UIKit
class CompanyDetailsViewController: UIViewController,UITableViewDelegate, UITableViewDataSource {
var tableView: UITableView = UITableView()
let animals : [String] = ["Dogs","Cats","Mice"]
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.blue
}
//code for create table view, etc
}
Solved! It was an error with the button's selector call because I didn't know the selector call was changed with Swift 3.
class CompanyViewController: UIViewController {
override func viewDidLoad() {
button.addTarget(self, action: #selector(CompanyViewController.detailsButtonTapped(button:)), for: .touchUpInside)
}
func detailsButtonTapped(button: UIButton!) {
let companyDetailsViewController = CompanyDetailsViewController()
self.navigationController?.pushViewController(companyDetailsViewController, animated: true)
}
}
I have 2 classes, the first class is named "store" where I create a button which calls a method: "storeSelected" located in the second class with the name ExploreViewController.
The method should print "done" and take me to another view controller.
Without the segue "done" is printed but when putting the segue code the app crashes.
The error is:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Receiver (<Glam.ExploreViewController: 0x14ed3be20>) has no segue with identifier 'ok''
.........
// ExploreViewController Class
let _sharedMonitor: ExploreViewController = { ExploreViewController() }()
class ExploreViewController: UIViewController, UIScrollViewDelegate {
class func sharedMonitor() -> ExploreViewController {
return _sharedMonitor
}
func storeSelected(sender: UIButton) {
println("done") // it entered here and "done" is printed
self.performSegueWithIdentifier("ok", sender: self) //here is the problem
}
}
// another class named "Store"
// button is created
let monitor = ExploreViewController.sharedMonitor()
btn.addTarget(monitor, action: Selector("storeSelected:"), forControlEvents: UIControlEvents.TouchUpInside)
Assuming that you have a view controller where you create the Store object - you should pass the button action back to this view controller and add a segue from it to your desired destination.
Best practice is to use a protocol that delegates the buttons action back up to the viewController that it is contained in as below.
Store.swift
protocol StoreDelegate: NSObject {
func didPressButton(button: UIButton)
}
class Store: UIView {
weak var delegate: StoreDelegate!
override init(frame: CGRect) {
super.init(frame: frame)
var button = UIButton()
button.setTitle(
"button",
forState: .Normal
)
button.addTarget(
self,
action: "buttonPress:",
forControlEvents: .TouchUpInside
)
self.addSubview(button)
}
func buttonPress(button: UIButton) {
delegate.didPressButton(button)
}
}
ViewController.swift
class ViewController: UIViewController, StoreDelegate {
override func viewDidLoad() {
super.viewDidLoad()
addStoreObj()
}
func addStoreObj() {
var store = Store()
store.delegate = self // IMPORTANT
self.view.addSubview(store)
}
func didPressButton(button: UIButton) {
self.performSegueWithIdentifier(
"ok",
sender: nil
)
}
}
This code is untested, but I hope you get the idea - your Store object delegates the button press activity back to its containing ViewController and then the ViewController carries out the segue that you have attached to it in the Storyboard.