Private action methods in swift crashes - ios

In objective-c I define my action methods in .m file. So they become private(sort of) and other classes (view controllers) can't see them.
In swift I tried to do it. But app crashed with unrecognized selector sent to instance error. Here is the action source object.
let rememberSwitch = UISwitch()
rememberSwitch.addTarget(self, action: "rememberMeChanged:", forControlEvents: .ValueChanged)
and here is the action method.
private func rememberMeChanged(rememberSwitch: UISwitch) {
}
Is there another way to make this method hidden from other classes other than define it as private?

Related

How to subscribe method into button touch outside of class that contains this method?

I have SomeViewController with a single button on the view. In didLoad I add method from the object as the handler of button touch in the following way:
class SomeViewController: UIViewController {
func didLoad() {
// ...
let x = X()
button.addTarget(self, action: #selector(x.test),
for: UIControl.Event.touchUpInside)
// ...
}
class X {
#objc func test(sender:UIButton) {
// ...
}
}
#objc func test(sender:UIButton) {
// ...
}
}
I am Swift newcomer and have no experience with Objective-C. But when I found out that button touch calls SomeViewController.test that broke my expectation based on my previous experience in other programming languages. As for me, it looks like a bug. After deleting SomeViewController.test, button touch throws the exception:
2019-03-07 13:07:25.737090+0200 myProgram[13570:283951]
-[myProgram.SomeViewController testWithSender:]: unrecognized selector sent to instance 0x7fbcc9d174d0 2019-03-07
13:07:25.745918+0200 myProgram[13570:283951] *** Terminating app due to
uncaught exception 'NSInvalidArgumentException', reason:
'-[myProgram.SomeViewController testWithSender:]: unrecognized
selector sent to instance 0x7fbcc9d174d0'
What is the principle of subscribing to an event with wrapper #selector?
How to subscribe method into button touch outside of class that contains this method? If it isn't possible - Why?
swift 4.2
Replace
button.addTarget(self, action: #selector(x.test),
for: UIControl.Event.touchUpInside)
with
button.addTarget(x, action: #selector(x.test),
for: UIControl.Event.touchUpInside)
As the target should contain the implementation of the selector method. Also, there is necessary to protect x from garbage collecting, it's better to make
let x = X()
an instance variable

Accessing uibutton action from a different class Swift

I have an UITableView that is loaded from two custom cell xib files.
The second one contains an UIButton.
But since i've added the custom class for xib - it has its own actions and functions, which i cannot access from ViewController.
My goal is to apply some action on UIButton when the custom cell is loaded in the tableview.
My function is defined in ViewController (because all variables are there) and my UIButton action is defined in Custom class for xib.
How do i connect one to another?
Thank you
Here is the solution in swift
If you want to perform action in another class when an event takes place in another class, then you have to use Protocols in your code, so that you can perform the action in another class.
For example
Declare your protocol like this before the class interface
protocol MyDelegateClass {
func btnAction()
}
Define your protocol in the interface of your class like this
var MyDelegateClass! = nil
Now on your button action trigger the protocol like this
#IBAction func btnProtocolAction(sender: AnyObject) {
[delegate btnAction];
}
Now include the protocol in the class like this
class myActionClass: UIViewController, PopUpViewDelegate {
Now assign the protocol to the MyDelegateClass object like this
myProtocolObject.delegate=self
Also define the class which you have declared in MyDelegateClass like
func btnAction() {
print(#"This method will triggered");
}
Hope this helps you.
You can achieve this by simply posting a notification.
NSNotificationCenter.defaultCenter().postNotificationName("buttonClickedNotification", object: nil)
Post this notification from your button method in custom class. You can also pass any data by using object parameter (Here it is nil).
And observe the notification in your viewController.
NSNotificationCenter.defaultCenter().addObserver(self, selector: "buttonClicked:", name: "buttonClickedNotification", object: nil)
And implement buttonClicked() method in your viewController.
func buttonClicked(data: NSNotification)
{
//If any data is passed get it using
let receivedData:NSDictionary = data.object as! NSDictionary //If data is of NSDictionary type.
}
Write a delegate method. This will connect your ViewController and Custom Class. What have you tried already?

Swift: Are addTarget actions only allowing class methods?

I tried to add a target to a UIButton and stumbled upon a weird behaviour
if i try:
//h = a collection view header
switch myVar {
case "none":
h.button.addTarget(self, action: "buttonTapped:", forControlEvents: .TouchUpInside)
func buttonTapped(sender:AnyObject) {
sendFriendRequest(self.targetUser,nil
}
}
I get SIGABRT - with "selector not found"
but if I move the function out of the switch case and make it a method of my ViewController, everything works as expected.
Anyone has an explanation for that? Is is just not allowed or are there technical reasons?
Selectors don't need to be class functions, but they must visible to the object call the selector (i.e. can't be marked as private or inside a method).
You can call any method in the project by using other class's instance instead of self.
Check this answer for the details.
https://stackoverflow.com/a/33068386/2125010

Responder Chain in Swift (nil target in UIButton target)

I have got a problem using the responder chain in swift.
When I setup a buttons target using a nil target like:
someButton.addTarget(nil, action:"addButtonTapped:", forControlEvents: .TouchUpInside)
The action will be send up the responder chain until the action is handled in the controller. So far so good :-)
But I want to intercept the action, execute some code and relay it on to the controller. But I cannot find a way to do this in swift. In ObjC this task is easy to do so I guess there should be a way in swift too.
Thanks in advance for any help :-)
One of my co-workers gave me the hint to recreate the selector and send it manually again.
let selector = Selector("someButtonTapped:")
let target: AnyObject? = self.nextResponder()?.targetForAction(selector, withSender: button)
UIApplication.sharedApplication().sendAction(selector, to: target, from: self, forEvent: nil)
This recreates the responder chain and relays the new message to the next responder.
I hope that somebody will find this useful.
I wanted to show a different view controller after dismissing the current one. The
MyContainerViewController container view controller has a function to open a different view controller. Using the responder chain to present a different view controller after dismissing the current one avoids having to keep references or casting the parent view controller. This is especially convenient
when using lots of nested child and container view controllers.
class SomeChildViewController: UIViewController {
#IBAction func closeAndShowSomething(sender: Any?) {}
let showSelector = #selector(MyContainerViewController.showSomething(_:))
let viewController: Any? = next?.target(forAction: showSelector, withSender: nil)
dismiss(animated: true) {
UIApplication.shared.sendAction(showSelector, to: viewController, from: self, for: nil)
}
}
}

Swift: Best way to get value from view

I have a custom UIView (called GridView) that I initialize and then add to a ViewController (DetailViewController). GridView contains several UIButtons and I would like to know in DetailViewController when those buttons are touched. I'm new to Swift and am wondering what is the best pattern to use to get those events?
If you want to do this with notifications, use 1:
func postNotificationName(_ notificationName: String,
object notificationSender: AnyObject?)
in the method that is triggered by your button. Then, in your DetailViewController, add a listener when it is initialized with 2:
func addObserver(_ notificationObserver: AnyObject,
selector notificationSelector: Selector,
name notificationName: String?,
object notificationSender: AnyObject?)
Both functions can be called from NSNotificationCenter.defaultCenter().
Another method would be to add callbacks which you connect once you initialize the GridView in your DetailViewController. A callback is essentially a closure:
var callback : (() -> Void)?
which you can instantiate when needed, e.g.
// In DetailViewController initialization
gridView = GridView()
gridView.callback = { self.doSomething() }
In GridView you can trigger the callback like this:
func onButton()
{
callback?()
}
The callback will only execute, if unwrapping succeeds. Please ensure, that you have read Automatic Reference Counting, because these constructs may lead to strong reference cycles.
What's the difference? You can connect the callback only once (at least with the method I've showed here), but when it triggers, the receiver immediately executes its code. For notifications, you can have multiple receivers but there is some delay in event delivery.
Lets assume your GridView implementation is like as follows:
class GridView : UIView {
// Initializing buttons
let button1:UIButton = UIButton(...)
let button2:UIButton = UIButton(...)
// ...
// Adding buttons to view
self.addSubview(button1)
self.addSubview(button2)
// ...
}
Now, we will add selector methods which will be called when a button is touched. Lets assume implementation of your view controller is like as follows:
class DetailViewController : UIViewController {
let myView:GridView = GridView(...)
myView.button1.addTarget(self, action: "actionForButton1:", forControlEvents: UIControlEvents.TouchUpInside)
myView.button2.addTarget(self, action: "actionForButton2:", forControlEvents: UIControlEvents.TouchUpInside)
// ...
func actionForButton1(sender: UIButton!) {
// Your actions when button 1 is pressed
}
// ... Selectors for other buttons
}
I have to say that my example approach is not a good example for encapsulation principles of Object-Oriented Programming, but I have written like this because you are new to Swift and this code is easy to understand. If you want to prevent duplicate codes such as writing different selectors for each button and if you want to set properties of your view as private to prevent access from "outside" like I just did in DetailViewController, there are much much better solutions. I hope it just helps you!
I think you better create a class called GridView that is inherited from the UIView. Then, you can connect all you UI element with you class as IBOutlet or whatever using tag something like that. Later on, you can ask the instance of GridView in DetailViewController so that you can connect as IBAction.
Encapsulation is one of the principles of OOP.

Resources