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?
Related
I have 3 classes :
Core that doesn't inherit from any class.
vcMain and vcIncomingFile which both inherit from UIViewController.
I have a segue from vcMain to vcIncomingFile.
How can I call the -performSegueWithIdentifier: method from the Core class,between vcMain and vcIncoming. I mean I want to have a method or delegate or anything else in Core class that can performSegue from vcMain to vcIncomingFile.
class Core {
func showIncomingVC(){ }
}
in showIncomingVC function, I want to performSegue between vcMain and vcIncomingFile.
Thanks
To implement the patterns properly we would need to know if your Core class is part of your model or if it is some kind of control flow mechanism.
If it is part of the model, it should know nothing about the view controllers but the view controllers are allowed to access it. In this case your view controllers could implement a delegate protocol of some sort and establish a call path from your Core class by assigning themselves as a delegate for some part of the Core functionality. The Core could then trigger any pre-defined behaviour in its delegates by calling the methods defined in the protocol.
For example,
If you define a protocol called FileEventDelegate with a method named fileReceived().
Then, add a member to your Core class called fileEventDelegate of type FileEventDelegate?
Whenever the Core class receives a file, it can call fileEventDelegate?.fileReceived(). The object instance, of whichever class implements the protocol, that registered itself as the delegate will handle it from there.
A unit testing class could also be the delegate and not even have nor need a segue to be performed.
Your Core class could even work without a delegate being set.
On the UI side, your VcMain class can implement the FileEventDelegate protocol by defining a fileReceived() function that call performSegueWithIdentifier(...). On viewLoaded() it can set itself as the fileEventDelegate of the Core class instance it is working with.
This keeps all model-to-visual logic in the viewController where it belongs.
If your Core class works in complete separation of the view controllers (meaning that the view controllers don't know how to access the instance(s) of Core), you may want to look into NSNotificationCenter and send notifications out in the universe for your viewControllers to pick-up asynchronously.
Core needs to be of UIViewController or some class that inherit UIViewController because performSegueWithIdentifier is a function of UIViewController
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewController_Class/index.html#//apple_ref/occ/instm/UIViewController/performSegueWithIdentifier:sender:
You can work with protocol instead. Create a protocol in your Core class with func showIncomingVC(){ } and use its delegate in your view class
Just do like this:
struct Segue {
let sourceVC: UIViewController
let sender: AnyObject
let identifier: String
}
protocol SegueProtocol: class {
var segue: Segue { get }
}
class Core {
weak var delegate: SegueProtocol?
func perform() {
guard let segue = delegate?.segue else {
return
}
segue.sourceVC.performSegueWithIdentifier(segue.identifier, sender: segue.sender)
}
}
I'm learning Swift and I'm studying the delegation pattern.
I think I understand exactly what is delegation and how it works, but I have a question.
I have a situation where Controller A is the delegate for Controller B.
In controller B I define a delegate protocol.
In controller B I set a variable delegate (optional)
In controller B I send message when something happens to the delegate
Controller A must adopt method of my protocol to become a delegate
I cannot understand if every delegate controller (in this case A) listens for messages sent by controller B or If I have to tell to controller B that A is now his delegate.
I notice that someone use this code (in controller A)
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "Example" {
let navigationController = segue.destinationViewController as UINavigationController
let controller = navigationController.topViewController as AddItemViewController
controller.delegate = self
}
}
Is this the only way to tell a delegator who is his delegate?
I believe, you need to tell a deligator who is its delegate upon creation of that it. Now, the delegator can be created programatically or through storyboard. So, based on that you have two options, you can tell it who is its delegator programatically like you showed in the code or from IB.
The key here is upon creation. Let's me explain myself. Take the case of a UIView. Say, you want a Custom UIView object(CustomView). So, you drag and drop a UIView in your View Controller and in the identity inspector, you assign its class as of your CustomView's class. So, basically, as soon as the controller is created, your custom view will also be created. Now, you can either say it that the View Controller in which it is created is its delegate or You can go to the IB and connect the view's delegate to the View Controller.
Now, let's assume that you wanted the custom view to be created in your ViewController programatically. In that case, you would probably call the -initWithFrame: method to create the view and upon creation you tell that delegator that who is its delegate like-
myCustomView.delegate = self;
same goes with a View Controller.
controller.delegate = self;
So, basically to tell a delegator who is its delegate, you first need that delegator to be created. At least, that's what I think.
I think one of the best example of delegation is UITableView.
Whenever you want the control of various properties of a tableView e.g. rowHeight etc, you set your controller to be the delegate of your tableview. To set the delegate of your tableView you need to have tableView created obviously as pointed out by #natasha.
So in your case, you can set delegate of your delegator when you create it or when you find a need for the controller to be delegate of your delegator but you definitely need your delegator to be present to set its property.
You can set your controller as delegate at any time when you require control.
I'm sure you want your UIViewController to act like described, but here is a simpler example how to use the delegation pattern with custom classes:
protocol ControllerBDelegate: class {
func somethingHappendInControllerB(value: String)
/* not optional here and passes a value from B to A*/
/* forces you to implement the function */
}
class ControllerB {
var delegate: ControllerBDelegate?
private func someFunctionThatDoSomethingWhenThisControllerIsAlive() {
/* did some magic here and now I want to tell it to my delegate */
self.delegate?.somethingHappendInControllerB(value: "hey there, I'm a magician")
}
func doSomething() {
/* do something here */
self.someFunctionThatDoSomethingWhenThisControllerIsAlive()
/* call the function so the magic can really happen in this example */
}
}
class ControllerA: ControllerBDelegate {
let controllerB = ControllerB()
init() {
self.controllerB.delegate = self /* lets say we add here our delegate*/
self.controllerB.doSomething() /* tell your controller B to do something */
}
func somethingHappendInControllerB(value: String) {
print(value) /* should print "hey there, I'm a magician" */
}
}
I wrote the code from my mind and not testet it yet, but you should get the idea how to use such a pattern.
I'm, relatively, a beginner, so this may be an entirely common practice—or an entirely impossible one—but I've been wondering if it's possible to modify a view controller added in a storyboard so that instead of (or in addition to?) being an instance of UIViewConroller, it's also an instance of (blahblah)ViewController, e.g. ABUnknownPersonViewController.
That way, instead of doing something like this:
class ViewController : UIViewController {
override func viewDidLoad() {
let test = ABUnknownPersonViewController()
...
self.presentViewController(test, animated: false, completion: nil)
}
}
This could be done:
class ViewController : ABUnknownPersonViewController {
override func viewDidLoad() {
//ViewController already is an ABUnknownPersonViewController, so you can treat it as one
//example below (displayedPerson is a property of ABUnkownPersonViewControllers)
self.displayedPerson...
}
}
EDIT: ABUnknownPersonViewController is a class supplied by Apple, which does not support subclassing (here). With that said, and the understanding that I would obviously like as simple a solution as possible (avoidance of protocols and whatnot), what are my options?
I tried class FourthViewController: UIViewController, ABUnknownPersonViewController, ABUnknownPersonViewController, ABUnknownPersonViewControllerDelegate only to get an error about multiple inheritance.
It sounds like what you actually want to do is to subclass UIViewController, and in your storyboard, set the custom class to your subclass. When the view controller is loaded from the storyboard, it will be an instance of your subclass.
So your subclass would look like this:
class ABUnknownPersonViewController : UIViewController {
override func viewDidLoad() {
self.displayedPerson...
}
}
In the storyboard, highlight the view controller you want to use a custom class for, and in the Custom Class field, type the name of your subclass. If you've done it correctly, it should autocomplete for you.
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.
I'm trying to pass data from the modal ViewController to his source ViewController. I think I have to use delegation but it doesn't work.
protocol communicationControllerCamera{
func backFromCamera()
}
class Camera: UIViewController{
var delegate: communicationControllerCamera
init(){
self.delegate.backFromCamera()
}
}
class SceneBuilder: UIViewController, communicationControllerCamera{
func backFromCamera(){ // Never called
println("YEAHH")
}
}
The backFromCamera method it's not called. What did I do wrong?
You didn't set a delegate so it was empty when you tried to call backFromCamera().
Here's a simple working example you can test out. Notice the use of the optional type (?) for the delegate.
// Camera class
protocol communicationControllerCamera {
func backFromCamera()
}
class Camera: UIViewController {
var delegate: communicationControllerCamera? = nil
override func viewDidLoad() {
super.viewDidLoad()
self.delegate?.backFromCamera()
}
}
// SceneBuilder class
class SceneBuilder: UIViewController, communicationControllerCamera {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
var myCamera = Camera()
myCamera.delegate = self
self.presentModalViewController(myCamera, animated: true)
}
func backFromCamera() {
println("Back from camera")
}
}
You can find all the information you need in Apple's Swift documentation.
Obviously the chosen answer is correct, but it didn't help me. I did successfully implement protocols though, so I wanted to provide my own explanation in case anyone is struggling with grasping the concept, like I was.
Protocol Code Is Written in Three Places:
Two ViewController Classes
The Protocol itself (code written outside of VC classes)
When I write my protocols, I put them in my "ToolBox" document and I still write comments to remind myself which VCs are doing what. Two examples:
So there is always:
The protocol code (shown above)
Code in a VC which initiates the action
Code in a VC which is delegated to carry out the action
1. The protocol code
See the image above for a reference. Essentially, the protocol code is just where you give the protocol a name and declare what functions you want to remotely call/delegate to. Name the protocol. Declare the names of the functions that can be called upon and declare their parameter types such as string, etc.
2. Code in a VC which initiates the action
This is the code that initiates the protocol. In this example, this is code from a table cell, which needs to delegate some work back to the main table VC. The first screenshot shows the creation of the delegate variable and the second screenshot is the actual use of that variable.
So the below code are table-cell buttons. They all need to trigger code outside of the cell VC, so they all trigger functions using the protocol I declared above.
3. Code in a VC which is delegated to carry out the action
Now the protocol is being called, but which VC answers the call? To answer that question, choose the VC and add the protocol name to the class declaration:
Lastly, you need the actual meat of the whole thing. Not the trigger, not the protocol itself, not the class declaration... but the actual function you want to call:
Hope This Helps
I don't know why protocols just wouldn't sink through my thick skull but they wouldn't. I hope this helps others like me!