Passing Data in Swift - model-view-controller

I have been looking for an answer for this, but have only found answers for segues.
I have viewController1 with a button that segues to viewController2. There is no code for this, I set it up through Interface builder. On viewController2 I have a button that dismisses itself with
self.dismissViewControllerAnimated(true, completion, nil)
I want to pass a string from viewController2 back to viewController1 when the view is dismissed. How do I go about doing this? Also, I am using swift.
Thanks in advance!

There are two common patterns, both of which eliminate the need for viewController2 to know explicitly about viewController1 (which is great for maintainability):
Create a delegate protocol for your for viewController2 and set viewController1 as the delegate. Whenever you want to send data back to viewController1, have viewController2 send the "delegate" the data
Setup a closure as a property that allows passing the data. viewController1 would implement that closure on viewController2 when displaying viewController2. Whenever viewController2 has data to pass back, it would call the closure. I feel that this method is more "swift" like.
Here is some example code for #2:
class ViewController2 : UIViewController {
var onDataAvailable : ((data: String) -> ())?
func sendData(data: String) {
// Whenever you want to send data back to viewController1, check
// if the closure is implemented and then call it if it is
self.onDataAvailable?(data: data)
}
}
class ViewController1 : UIViewController {
func doSomethingWithData(data: String) {
// Do something with data
}
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
// When preparing for the segue, have viewController1 provide a closure for
// onDataAvailable
if let viewController = segue.destinationViewController as? ViewController2 {
viewController.onDataAvailable = {[weak self]
(data) in
if let weakSelf = self {
weakSelf.doSomethingWithData(data)
}
}
}
}
}

I used the code from the first answer in a transition between controllers WITHOUT prepareForSegue and worked for me as well.
Here's the sample code.
The First View Controller:
#IBAction func dpAgendaClick(sender:UIBarButtonItem) {
///instantiating view controller with identifier
if let datePickerViewController = storyboard?.instantiateViewControllerWithIdentifier("DatePickerViewController")
as? DatePickerViewController {
///bring instantiated view controller to front
self.presentViewController(datePickerViewController, animated: true, completion: nil)
///wrapping the data returned
datePickerViewController.onDataFiltroAvailable = {[weak self]
(dataFiltro) in
if let weakSelf = self {
///use dataFiltro here
}
}
The second View Controller:
var onDataFiltroAvailable: ((dataFiltro: String) -> ())?
///private var
var dataFiltro: String = ""
///the returning data is obtained on the datePickerChanged event
#IBAction func datePickerChanged(sender: UIDatePicker) {
let dateFormatter = NSDateFormatter()
dateFormatter.dateStyle = NSDateFormatterStyle.ShortStyle
dateFormatter.dateFormat = "yyyy-MM-dd"
dataFiltro = dateFormatter.stringFromDate(datePicker.date)
}
///dismiss the controller on button click
#IBAction func dpOkClick(sender: UIButton) {
///"returning" the data
self.onDataFiltroAvailable?(dataFiltro: dataFiltro)
dismissViewControllerAnimated(true, completion: nil)
}

(Swift 2.1, Xcode 7, iOS9)
If you don't want it to be tightly coupled only between 2 ViewControllers,
You can also use the Notification Design Pattern (Post & Observe), which is mainly used to pass on the same object/information from one VC to multiple View Controllers.
For your scenario :
In VC2.swift :
#IBAction func BackBtn(sender: UIButton) {
NSNotificationCenter.defaultCenter().postNotificationName("ThisIsTheMessage", object: nil, userInfo:["ObjectBeingSent":yourObject])
}
And in VC1.swift :
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("yourFunction:"), name: "ThisIsTheMessage", object: nil)
}
func yourFunction(theNotification : NSNotification) {
if let extractInfo = theNotification.userInfo {
//code to use the object sent from VC2, by extracting the object details
}
}
Common Practise is:
Pass data forward -> Use PrepareForSegue
Pass data backward to the previous View Controller-> Protocol and Delegation
Pass data across multiple View Controllers -> Notifications : Post and Observe(observe in all the View controllers where you are using the object details)

Related

Obtaining a View Controller Reference

I read quite a few questions and answers no this problem. Some are for Ojective C. Some are for iOS. The ones that were close to what I need didn't work.
I've set up a protocol for delegation. It doesn't work. The problem is that delegate variable isn't set. I need the reference to an active controller.
Delegator
protocol SwitchTabDelegate: class {
func selectTab(tab: Int)
}
class ViewController: NSViewController {
weak var delegate: SwitchTabDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func selectCompositions(_ sender: NSButton) {
if let delegate = self.delegate {
delegate.selectTab(tab: 2)
}
else {
print("self.delegate is nil")
}
print("delegate called")
}
}
Delegatee
class TabViewController: NSTabViewController, SwitchTabDelegate {
var viewController : ViewController?;
override func viewDidLoad() {
super.viewDidLoad()
//viewController = storyboard?.instantiateController(withIdentifier: "viewController") as? ViewController
// viewController?.delegate = self
// print(viewController)
}
func selectTab(tab: Int) {
print("In the delegate")
switchToDataTab()
}
func switchToDataTab() {
Timer.scheduledTimer(timeInterval: 0.2, target: self,
selector: #selector(switchToDataTabCont),
userInfo: nil, repeats: false)
}
func switchToDataTabCont(){
self.selectedTabViewItemIndex = 2
}
}
The delegatee is the main NSViewContoller. On the storyboard, it contains two buttons and a Container view controller. Embedded in the container view controller is the TabViewController, the delegatee. You can see in the delegatee where I tried to get a reference. It does get a reference, presumably to the newly instantiated instance. I need a reference to the original view controller that was spun up when the application started.
Answer
I added the following code to the delegator:
override func prepare(for segue: NSStoryboardSegue, sender: Any?) {
let controller = segue.destinationController as! TabViewController
self.delegate = controller as SwitchTabDelegate
}
That's not how it should work following the design pattern. The delegator should have no knowledge of the delegatee. I've spent way too much time on this issue so a hack is going to do.
When using storyboards, you want to "push" references to children when they are created vs. pulling them from an upstream controller. This is what -prepareForSegue:sender: is used for.

Can I mix UIKit and TVMLKit within one app?

I'm exploring tvOS and I found that Apple offers nice set of templates written using TVML. I'd like to know if a tvOS app that utilises TVML templates can also use UIKit.
Can I mix UIKit and TVMLKit within one app?
I found a thread on Apple Developer Forum but it does not fully answer this question and I am going through documentation to find an answer.
Yes, you can. Displaying TVML templates requires you to use an object that controls the JavaScript Context: TVApplicationController.
var appController: TVApplicationController?
This object has a UINavigationController property associated with it. So whenever you see fit, you can call:
let myViewController = UIViewController()
self.appController?.navigationController.pushViewController(myViewController, animated: true)
This allows you to push a Custom UIKit viewcontroller onto the navigation stack. If you want to go back to TVML Templates, just pop the viewController off of the navigation stack.
If what you would like to know is how to communicate between JavaScript and Swift, here is a method that creates a javascript function called pushMyView()
func createPushMyView(){
//allows us to access the javascript context
appController?.evaluateInJavaScriptContext({(evaluation: JSContext) -> Void in
//this is the block that will be called when javascript calls pushMyView()
let pushMyViewBlock : #convention(block) () -> Void = {
() -> Void in
//pushes a UIKit view controller onto the navigation stack
let myViewController = UIViewController()
self.appController?.navigationController.pushViewController(myViewController, animated: true)
}
//this creates a function in the javascript context called "pushMyView".
//calling pushMyView() in javascript will call the block we created above.
evaluation.setObject(unsafeBitCast(pushMyViewBlock, AnyObject.self), forKeyedSubscript: "pushMyView")
}, completion: {(Bool) -> Void in
//done running the script
})
}
Once you call createPushMyView() in Swift, you are free to call pushMyView() in your javascript code and it will push a view controller onto the stack.
SWIFT 4.1 UPDATE
Just a few simple changes to method names and casting:
appController?.evaluate(inJavaScriptContext: {(evaluation: JSContext) -> Void in
and
evaluation.setObject(unsafeBitCast(pushMyViewBlock, to: AnyObject.self), forKeyedSubscript: "pushMyView" as NSString)
As mentioned in the accepted answer, you can call pretty much any Swift function from within the JavaScript context. Note that, as the name implies, setObject:forKeyedSubscript: will also accept objects (if they conform to a protocol that inherits from JSExport) in addition to blocks, allowing you to access methods and properties on that object. Here's an example
import Foundation
import TVMLKit
// Just an example, use sessionStorage/localStorage JS object to actually accomplish something like this
#objc protocol JSBridgeProtocol : JSExport {
func setValue(value: AnyObject?, forKey key: String)
func valueForKey(key: String) -> AnyObject?
}
class JSBridge: NSObject, JSBridgeProtocol {
var storage: Dictionary<String, String> = [:]
override func setValue(value: AnyObject?, forKey key: String) {
storage[key] = String(value)
}
override func valueForKey(key: String) -> AnyObject? {
return storage[key]
}
}
Then in your app controller:
func appController(appController: TVApplicationController, evaluateAppJavaScriptInContext jsContext: JSContext) {
let bridge:JSBridge = JSBridge();
jsContext.setObject(bridge, forKeyedSubscript:"bridge");
}
Then you can do this in your JS: bridge.setValue(['foo', 'bar'], "baz")
Not only that, but you can override views for existing elements, or define custom elements to use in your markup, and back them with native views:
// Call lines like these before you instantiate your TVApplicationController
TVInterfaceFactory.sharedInterfaceFactory().extendedInterfaceCreator = CustomInterfaceFactory()
// optionally register a custom element. You could use this in your markup as <loadingIndicator></loadingIndicator> or <loadingIndicator /> with optional attributes. LoadingIndicatorElement needs to be a TVViewElement subclass, and there are three functions you can optionally override to trigger JS events or DOM updates
TVElementFactory.registerViewElementClass(LoadingIndicatorElement.self, forElementName: "loadingIndicator")
Quick custom element example:
import Foundation
import TVMLKit
class LoadingIndicatorElement: TVViewElement {
override var elementName: String {
return "loadingIndicator"
}
internal override func resetProperty(resettableProperty: TVElementResettableProperty) {
super.resetProperty(resettableProperty)
}
// API's to dispatch events to JavaScript
internal override func dispatchEventOfType(type: TVElementEventType, canBubble: Bool, cancellable isCancellable: Bool, extraInfo: [String : AnyObject]?, completion: ((Bool, Bool) -> Void)?) {
//super.dispatchEventOfType(type, canBubble: canBubble, cancellable: isCancellable, extraInfo: extraInfo, completion: completion)
}
internal override func dispatchEventWithName(eventName: String, canBubble: Bool, cancellable isCancellable: Bool, extraInfo: [String : AnyObject]?, completion: ((Bool, Bool) -> Void)?) {
//...
}
}
And here's how to set up a custom interface factory:
class CustomInterfaceFactory: TVInterfaceFactory {
let kCustomViewTag = 97142 // unlikely to collide
override func viewForElement(element: TVViewElement, existingView: UIView?) -> UIView? {
if (element.elementName == "title") {
if (existingView != nil) {
return existingView
}
let textElement = (element as! TVTextElement)
if (textElement.attributedText!.length > 0) {
let label = UILabel()
// Configure your label here (this is a good way to set a custom font, for example)...
// You can examine textElement.style or textElement.textStyle to get the element's style properties
label.backgroundColor = UIColor.redColor()
let existingText = NSMutableAttributedString(attributedString: textElement.attributedText!)
label.text = existingText.string
return label
}
} else if element.elementName == "loadingIndicator" {
if (existingView != nil && existingView!.tag == kCustomViewTag) {
return existingView
}
let view = UIImageView(image: UIImage(named: "loading.png"))
return view // Simple example. You could easily use your own UIView subclass
}
return nil // Don't call super, return nil when you don't want to override anything...
}
// Use either this or viewForElement for a given element, not both
override func viewControllerForElement(element: TVViewElement, existingViewController: UIViewController?) -> UIViewController? {
if (element.elementName == "whatever") {
let whateverStoryboard = UIStoryboard(name: "Whatever", bundle: nil)
let viewController = whateverStoryboard.instantiateInitialViewController()
return viewController
}
return nil
}
// Use this to return a valid asset URL for resource:// links for badge/img src (not necessary if the referenced file is included in your bundle)
// I believe you could use this to cache online resources (by replacing resource:// with http(s):// if a corresponding file doesn't exist (then starting an async download/save of the resource before returning the modified URL). Just return a file url for the version on disk if you've already cached it.
override func URLForResource(resourceName: String) -> NSURL? {
return nil
}
}
Unfortunately, view/viewControllerForElement: will not be called for all elements. Some of the existing elements (like collection views) will handle the rendering of their child elements themselves, without involving your interface factory, which means you'll have to override a higher level element, or maybe use a category/swizzling or UIAppearance to get the effect you want.
Finally, as I just implied, you can use UIAppearance to change the way certain built-in views look. Here's the easiest way to change the appearance of your TVML app's tab bar, for example:
// in didFinishLaunching...
UITabBar.appearance().backgroundImage = UIImage()
UITabBar.appearance().backgroundColor = UIColor(white: 0.5, alpha: 1.0)
If you already have a native UIKit app for tvOS, but would like to extend it by using TVMLKit for some part of it, You can.
Use the TVMLKit as a sub app in your native tvOS app. The following app shows how to do this, by retaining the TVApplicationController and present the navigationController from the TVApplicationController. The TVApplicationControllerContext is used to transfer data to the JavaScript app, as the url is transferred here :
class ViewController: UIViewController, TVApplicationControllerDelegate {
// Retain the applicationController
var appController:TVApplicationController?
static let tvBaseURL = "http://localhost:9001/"
static let tvBootURL = "\(ViewController.tvBaseURL)/application.js"
#IBAction func buttonPressed(_ sender: UIButton) {
print("button")
// Use TVMLKit to handle interface
// Get the JS context and send it the url to use in the JS app
let hostedContContext = TVApplicationControllerContext()
if let url = URL(string: ViewController.tvBootURL) {
hostedContContext.javaScriptApplicationURL = url
}
// Save an instance to a new Sub application, the controller already knows what window we are running so pass nil
appController = TVApplicationController(context: hostedContContext, window: nil, delegate: self)
// Get the navigationController of the Sub App and present it
let navc = appController!.navigationController
present(navc, animated: true, completion: nil)
}
Yes. See the TVMLKit Framework, whose docs start with:
The TVMLKit framework enables you to incorporate JavaScript and TVML files in your binary apps to create client-server apps.
From a quick skim of those docs, it looks like you use the various TVWhateverFactory classes to create UIKit views or view controllers from TVML, after which you can insert them into a UIKit app.

Add completion handler to presentViewControllerAsSheet(NSViewController)?

I am attempting to present a sheet configuration view (AddSoundEffect) for my main window/view controller (I'm using storyboards), and when the configuration view controller is dismissed, take the values entered in the AddSoundEffect view and pass that back to the main view. My current code in the main view controller:
presentViewControllerAsSheet(self.storyboard!.instantiateControllerWithIdentifier("AddSoundEffect") as! AddSoundViewController
And in the AddSoundViewController.swift file, the code to dismiss it is:
self.dismissViewController(self)
To pass the data, I have a class-independent tuple that I save data to. How do I add a completion handler to presentViewControllerAsSheet, and (optionally) is there a better way to pass the data between view controllers?
Setup: Xcode version 6.4, OS X 10.10.4
Delegation pattern is the easiest way for you.
// Replace this with your tuple or whatever data represents your sound effect
struct SoundEffect {}
protocol AddSoundViewControllerDelegate: class {
func soundViewController(controller: AddSoundViewController, didAddSoundEffect: SoundEffect)
}
//
// Let's say this controller is a modal view controller for adding new sound effects
//
class AddSoundViewController: UIViewController {
weak var delegate: AddSoundViewControllerDelegate?
func done(sender: AnyObject) {
// Dummy sound effect info, replace it with your own data
let soundEffect = SoundEffect()
//
// Call it whenever you would like to inform presenting view controller
// about added sound effect (in case of Done, Add, ... button tapped, do not call it
// when user taps on Cancel to just dismiss AddSoundViewController)
//
self.delegate?.soundViewController(self, didAddSoundEffect: soundEffect)
// Dismiss self
self.dismissViewControllerAnimated(true, completion: {})
}
}
//
// Let's say this controller is main view controller, which contains list of all sound effects,
// with button to add new sound effect via AddSoundViewController
//
class SoundEffectsViewController: UIViewController, AddSoundViewControllerDelegate {
func presentAddSoundEffectController(sender: AnyObject) {
if let addSoundController = self.storyboard?.instantiateViewControllerWithIdentifier("AddSoundEffect") as? AddSoundViewController {
addSoundController.delegate = self
self.presentViewController(addSoundController, animated: true, completion: {})
}
}
func soundViewController(controller: AddSoundViewController, didAddSoundEffect: SoundEffect) {
// This method is called only when new sound effect is added
}
}
Another way is to use closures:
// Replace this with your tuple or whatever data represents your sound effect
struct SoundEffect {}
//
// Let's say this controller is a modal view controller for adding new sound effects
//
class AddSoundViewController: UIViewController {
var completionHandler: ((SoundEffect) -> ())?
func done(sender: AnyObject) {
// Dummy sound effect info, replace it with your own data
let soundEffect = SoundEffect()
//
// Call it whenever you would like to inform presenting view controller
// about added sound effect (in case of Done, Add, ... button tapped, do not call it
// when user taps on Cancel to just dismiss AddSoundViewController)
//
self.completionHandler?(soundEffect)
// Dismiss self
self.dismissViewControllerAnimated(true, completion: {})
}
}
//
// Let's say this controller is main view controller, which contains list of all sound effects,
// with button to add new sound effect via AddSoundViewController
//
class SoundEffectsViewController: UIViewController {
func presentAddSoundEffectController(sender: AnyObject) {
if let addSoundController = self.storyboard?.instantiateViewControllerWithIdentifier("AddSoundEffect") as? AddSoundViewController {
addSoundController.completionHandler = { [weak self] (soundEffect) -> () in
// Called when new sound effect is added
}
self.presentViewController(addSoundController, animated: true, completion: {})
}
}
}
Or many other ways like sending notification, ... Whatever suits your needs. But delegation pattern or closures is the best way to go in this specific case.
I missed that your question is about NSViewController. This example is for iOS, but same pattern can be used on OS X without any issues.
The easiest way to detect sheet opening or closing is to use the Sheet Notifications:
class ViewController: NSViewController, NSWindowDelegate {
override func viewDidLoad(){
NSApplication.sharedApplication().windows.first?.delegate = self
}
func windowDidEndSheet(notification: NSNotification) {
}
func windowWillBeginSheet(notification: NSNotification) {
}
}

How do I get the return key to perform the same action as a button press in Swift?

I want to know how you allow an action to be made by either pressing the return key on the software keyboard or by tapping a UIButton.
The UI button is already set up to perform an IBAction.
How do I also allow users to press the return key on the keyboard to perform the same action?
Make sure your class extends the UITextFieldDelegate protocol
SomeViewControllerClass : UIViewController, UITextFieldDelegate
You can perform action as follows:
override func viewDidLoad() {
super.viewDidLoad()
self.textField.delegate = self
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
//textField code
textField.resignFirstResponder() //if desired
performAction()
return true
}
func performAction() {
//action events
}
UPDATE
If your deployment target is iOS 9.0 or later, you can connect the “Primary Action Triggered” event of your text field to an action, like this:
ORIGINAL
Make your view controller adopt the UITextFieldDelegate protocol.
Set your text field's delegate to your view controller.
Implement textFieldShouldReturn: to call your action.
Swift 4.2 :
Other approach for the textfield created programmatically and doesn't need delegate :
MyTextField.addTarget(self, action: #selector(MyTextFielAction)
, for: UIControl.Event.primaryActionTriggered)
And then perform your action like below :
func MyTextFielAction(textField: UITextField) {
//YOUR CODE can perform same action as your UIButton
}
If your deployment target is iOS 9.0 or later, you can connect the “Primary Action Triggered” event of your text field to an action, like this:
I was not able to get the "Primary Action Triggered" to work as suggested. I used "Editing Did End" and that works for now Screenshot of Editing Did End
Here is a complete example, with both:
button-action to write and also to clear label and text when pressing button repeatedly it alternates both actions
return-in-keyboard when pressing key it triggers action and also resigns first responder
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var textField1: UITextField!
#IBOutlet weak var label1: UILabel!
var buttonHasBeenPressed = false
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
textField1.delegate = self
}
#IBAction func buttonGo(_ sender: Any) {
performAction()
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
performAction()
return true
}
func performAction() {
buttonHasBeenPressed = !buttonHasBeenPressed
if buttonHasBeenPressed == true {
label1.text = textField1.text
} else {
textField1.text = ""
label1.text = ""
}
}
}

Delegates in swift?

How does one go about making a delegate, i.e. NSUserNotificationCenterDelegate in swift?
Here's a little help on delegates between two view controllers:
Step 1: Make a protocol in the UIViewController that you will be removing/will be sending the data.
protocol FooTwoViewControllerDelegate:class {
func myVCDidFinish(_ controller: FooTwoViewController, text: String)
}
Step2: Declare the delegate in the sending class (i.e. UIViewcontroller)
class FooTwoViewController: UIViewController {
weak var delegate: FooTwoViewControllerDelegate?
[snip...]
}
Step3: Use the delegate in a class method to send the data to the receiving method, which is any method that adopts the protocol.
#IBAction func saveColor(_ sender: UIBarButtonItem) {
delegate?.myVCDidFinish(self, text: colorLabel.text) //assuming the delegate is assigned otherwise error
}
Step 4: Adopt the protocol in the receiving class
class ViewController: UIViewController, FooTwoViewControllerDelegate {
Step 5: Implement the delegate method
func myVCDidFinish(_ controller: FooTwoViewController, text: String) {
colorLabel.text = "The Color is " + text
controller.navigationController.popViewController(animated: true)
}
Step 6: Set the delegate in the prepareForSegue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "mySegue" {
let vc = segue.destination as! FooTwoViewController
vc.colorString = colorLabel.text
vc.delegate = self
}
}
And that should work. This is of course just code fragments, but should give you the idea. For a long explanation of this code you can go over to my blog entry here:
segues and delegates
If you are interested in what's going on under the hood with a delegate I did write on that here:
under the hood with delegates
Delegates always confused me until I realized that a delegate is just a class that does some work for another class. It's like having someone else there to do all the dirty work for you that you don't want to do yourself.
I wrote a little story to illustrate this. Read it in a Playground if you like.
Once upon a time...
// MARK: Background to the story
// A protocol is like a list of rules that need to be followed.
protocol OlderSiblingDelegate: class {
// The following command (ie, method) must be obeyed by any
// underling (ie, delegate) of the older sibling.
func getYourNiceOlderSiblingAGlassOfWater()
}
// MARK: Characters in the story
class BossyBigBrother {
// I can make whichever little sibling is around at
// the time be my delegate (ie, slave)
weak var delegate: OlderSiblingDelegate?
func tellSomebodyToGetMeSomeWater() {
// The delegate is optional because even though
// I'm thirsty, there might not be anyone nearby
// that I can boss around.
delegate?.getYourNiceOlderSiblingAGlassOfWater()
}
}
// Poor little sisters have to follow (or at least acknowledge)
// their older sibling's rules (ie, protocol)
class PoorLittleSister: OlderSiblingDelegate {
func getYourNiceOlderSiblingAGlassOfWater() {
// Little sis follows the letter of the law (ie, protocol),
// but no one said exactly how she had to respond.
print("Go get it yourself!")
}
}
// MARK: The Story
// Big bro is laying on the couch watching basketball on TV.
let bigBro = BossyBigBrother()
// He has a little sister named Sally.
let sally = PoorLittleSister()
// Sally walks into the room. How convenient! Now big bro
// has someone there to boss around.
bigBro.delegate = sally
// So he tells her to get him some water.
bigBro.tellSomebodyToGetMeSomeWater()
// Unfortunately no one lived happily ever after...
// The end.
In review, there are three key parts to making and using the delegate pattern.
the protocol that defines what the worker needs to do
the boss class that has a delegate variable, which it uses to tell the worker class what to do
the worker class that adopts the protocol and does what is required
Real life
In comparison to our Bossy Big Brother story above, delegates are often used for the following practical applications:
Communication: one class needs to send some information to another class.
Code example 1: sending data from one view controller to another
Code example 2: sending text input from a custom keyboard to a text field
Customization: one class wants to allow another class to customize it.
The great part is that these classes don't need to know anything about each other beforehand except that the delegate class conforms to the required protocol.
I highly recommend reading the following two articles. They helped me understand delegates even better than the documentation did.
What is Delegation? – A Swift Developer’s Guide
How Delegation Works – A Swift Developer’s Guide
One more note
Delegates that reference other classes that they do not own should use the weak keyword to avoid strong reference cycles. See this answer for more details.
It is not that different from obj-c.
First, you have to specify the protocol in your class declaration, like following:
class MyClass: NSUserNotificationCenterDelegate
The implementation will look like following:
// NSUserNotificationCenterDelegate implementation
func userNotificationCenter(center: NSUserNotificationCenter, didDeliverNotification notification: NSUserNotification) {
//implementation
}
func userNotificationCenter(center: NSUserNotificationCenter, didActivateNotification notification: NSUserNotification) {
//implementation
}
func userNotificationCenter(center: NSUserNotificationCenter, shouldPresentNotification notification: NSUserNotification) -> Bool {
//implementation
return true
}
Of course, you have to set the delegate. For example:
NSUserNotificationCenter.defaultUserNotificationCenter().delegate = self;
I got few corrections to post of #MakeAppPie
First at all when you are creating delegate protocol it should conform to Class protocol. Like in example below.
protocol ProtocolDelegate: class {
func myMethod(controller:ViewController, text:String)
}
Second, your delegate should be weak to avoid retain cycle.
class ViewController: UIViewController {
weak var delegate: ProtocolDelegate?
}
Last, you're safe because your protocol is an optional value. That means its "nil" message will be not send to this property. It's similar to conditional statement with respondToselector in objC but here you have everything in one line:
if ([self.delegate respondsToSelector:#selector(myMethod:text:)]) {
[self.delegate myMethod:self text:#"you Text"];
}
Above you have an obj-C example and below you have Swift example of how it looks.
delegate?.myMethod(self, text:"your Text")
Here's a gist I put together. I was wondering the same and this helped improve my understanding. Open this up in an Xcode Playground to see what's going on.
protocol YelpRequestDelegate {
func getYelpData() -> AnyObject
func processYelpData(data: NSData) -> NSData
}
class YelpAPI {
var delegate: YelpRequestDelegate?
func getData() {
println("data being retrieved...")
let data: AnyObject? = delegate?.getYelpData()
}
func processYelpData(data: NSData) {
println("data being processed...")
let data = delegate?.processYelpData(data)
}
}
class Controller: YelpRequestDelegate {
init() {
var yelpAPI = YelpAPI()
yelpAPI.delegate = self
yelpAPI.getData()
}
func getYelpData() -> AnyObject {
println("getYelpData called")
return NSData()
}
func processYelpData(data: NSData) -> NSData {
println("processYelpData called")
return NSData()
}
}
var controller = Controller()
DELEGATES IN SWIFT 2
I am explaining with example of Delegate with two viewControllers.In this case, SecondVC Object is sending data back to first View Controller.
Class with Protocol Declaration
protocol getDataDelegate {
func getDataFromAnotherVC(temp: String)
}
import UIKit
class SecondVC: UIViewController {
var delegateCustom : getDataDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func backToMainVC(sender: AnyObject) {
//calling method defined in first View Controller with Object
self.delegateCustom?.getDataFromAnotherVC(temp: "I am sending data from second controller to first view controller.Its my first delegate example. I am done with custom delegates.")
self.navigationController?.popViewControllerAnimated(true)
}
}
In First ViewController Protocol conforming is done here:
class ViewController: UIViewController, getDataDelegate
Protocol method definition in First View Controller(ViewController)
func getDataFromAnotherVC(temp : String)
{
// dataString from SecondVC
lblForData.text = dataString
}
During push the SecondVC from First View Controller (ViewController)
let objectPush = SecondVC()
objectPush.delegateCustom = self
self.navigationController.pushViewController(objectPush, animated: true)
First class:
protocol NetworkServiceDelegate: class {
func didCompleteRequest(result: String)
}
class NetworkService: NSObject {
weak var delegate: NetworkServiceDelegate?
func fetchDataFromURL(url : String) {
delegate?.didCompleteRequest(result: url)
}
}
Second class:
class ViewController: UIViewController, NetworkServiceDelegate {
let network = NetworkService()
override func viewDidLoad() {
super.viewDidLoad()
network.delegate = self
network.fetchDataFromURL(url: "Success!")
}
func didCompleteRequest(result: String) {
print(result)
}
}
Very easy step by step (100% working and tested)
step1: Create method on first view controller
func updateProcessStatus(isCompleted : Bool){
if isCompleted{
self.labelStatus.text = "Process is completed"
}else{
self.labelStatus.text = "Process is in progress"
}
}
step2: Set delegate while push to second view controller
#IBAction func buttonAction(_ sender: Any) {
let secondViewController = self.storyboard?.instantiateViewController(withIdentifier: "secondViewController") as! secondViewController
secondViewController.delegate = self
self.navigationController?.pushViewController(secondViewController, animated: true)
}
step3: set delegate like
class ViewController: UIViewController,ProcessStatusDelegate {
step4: Create protocol
protocol ProcessStatusDelegate:NSObjectProtocol{
func updateProcessStatus(isCompleted : Bool)
}
step5: take a variable
var delegate:ProcessStatusDelegate?
step6: While go back to previous view controller call delegate method so first view controller notify with data
#IBAction func buttonActionBack(_ sender: Any) {
delegate?.updateProcessStatus(isCompleted: true)
self.navigationController?.popViewController(animated: true)
}
#IBAction func buttonProgress(_ sender: Any) {
delegate?.updateProcessStatus(isCompleted: false)
self.navigationController?.popViewController(animated: true)
}
Simple Example:
protocol Work: class {
func doSomething()
}
class Manager {
weak var delegate: Work?
func passAlong() {
delegate?.doSomething()
}
}
class Employee: Work {
func doSomething() {
print("Working on it")
}
}
let manager = Manager()
let developer = Employee()
manager.delegate = developer
manager.passAlong() // PRINTS: Working on it
Delegates are a design pattern that allows one object to send messages to another object when a specific event happens.
Imagine an object A calls an object B to perform an action. Once the action is complete, object A should know that B has completed the task and take necessary action, this can be achieved with the help of delegates!
Here is a tutorial implementing delegates step by step in swift 3
Tutorial Link
Here is real life delegate scenario
Lets make our own UITextField and UITextFieldDelegate
// THE MYSTERIOUS UITEXTFIELD
protocol UITextFieldDelegate {
func textFieldDidChange(_ textField: UITextField) -> Void
}
class UITextField {
var delegate: UITextFieldDelegate?
private var mText: String?
var text: String? {
get {
return mText
}
}
init(text: String) {
}
init() {
}
func setText(_ text: String) {
mText = text
delegate?.textFieldDidChange(self)
}
}
// HERE IS MY APP
class Main {
let textfield = UITextField()
func viewDidLoad() {
print("viewDidLoad")
textfield.delegate = self
textfield.setText("Hello")
}
}
extension Main: UITextFieldDelegate {
func textFieldDidChange(_ textField: UITextField) {
print(textField.text ?? "No string")
}
}
let main = Main()
main.viewDidLoad()
Here Simple Code Example of Delegate:
//MARK: - Protocol ShowResult
protocol ShowResult: AnyObject {
func show(value: Int)
}
//MARK: - MyOperation Class
class MyOperation {
weak var delegate: ShowResult?
func sum(fNumber: Int, sNumber: Int) {
delegate?.show(value: fNumber + sNumber)
}
}
//MARK: - ViewController Class
class ViewController: UIViewController,ShowResult {
var myOperation: MyOperation?
override func viewDidLoad() {
super.viewDidLoad()
loadMyOperation()
myOperation?.delegate = self
myOperation?.sum(fNumber: 100, sNumber: 20)
}
private func loadMyOperation() {
if myOperation == nil {
myOperation = MyOperation()
}
}
func show(value: Int) {
print("value: \(value)")
}
}
The solutions above seemed a little coupled and at the same time avoid reuse the same protocol in other controllers, that's why I've come with the solution that is more strong typed using generic type-erasure.
#noreturn public func notImplemented(){
fatalError("not implemented yet")
}
public protocol DataChangedProtocol: class{
typealias DataType
func onChange(t:DataType)
}
class AbstractDataChangedWrapper<DataType> : DataChangedProtocol{
func onChange(t: DataType) {
notImplemented()
}
}
class AnyDataChangedWrapper<T: DataChangedProtocol> : AbstractDataChangedWrapper<T.DataType>{
var base: T
init(_ base: T ){
self.base = base
}
override func onChange(t: T.DataType) {
base.onChange(t)
}
}
class AnyDataChangedProtocol<DataType> : DataChangedProtocol{
var base: AbstractDataChangedWrapper<DataType>
init<S: DataChangedProtocol where S.DataType == DataType>(_ s: S){
self.base = AnyDataChangedWrapper(s)
}
func onChange(t: DataType) {
base.onChange(t)
}
}
class Source : DataChangedProtocol {
func onChange(data: String) {
print( "got new value \(data)" )
}
}
class Target {
var delegate: AnyDataChangedProtocol<String>?
func reportChange(data:String ){
delegate?.onChange(data)
}
}
var source = Source()
var target = Target()
target.delegate = AnyDataChangedProtocol(source)
target.reportChange("newValue")
output: got new value newValue
In swift 4.0
Create a delegate on class that need to send some data or provide some functionality to other classes
Like
protocol GetGameStatus {
var score: score { get }
func getPlayerDetails()
}
After that in the class that going to confirm to this delegate
class SnakesAndLadders: GetGameStatus {
func getPlayerDetails() {
}
}
In swift 5
I am a beginner, I think this is easiest way to understand in practical scenario
Note:Any improvisations are most appreciated
protocol APIService {
func onSuccessResponse() -> AnyObject
func onFailureResponse() -> AnyObject
}
class APIHelper{
var delegate : APIService?
func postUsersDataAPI() {
//assuming API communication is success
if(success){
let _: AnyObject? = delegate?.onSuccessResponse()
}else if(failure){
let _: AnyObject? = delegate?.onFailureResponse()
}
}
func getAllUsersAPI() {
//assuming API communication is success
if(success){
let _: AnyObject? = delegate?.onSuccessResponse()
}else if(failure){
let _: AnyObject? = delegate?.onFailureResponse()
}
}
}
class ViewController:UIViewController,APIService {
func onSuccessResponse() -> AnyObject {
print("onSuccessResponse") as AnyObject
}
func onFailureResponse() -> AnyObject {
print("onFailureResponse") as AnyObject
}
#IBAction func clickBtnToPostUserData(_ sender: Any) {
let apiHelper = APIHelper()
apiHelper.delegate = self
apiHelper.postAPI()
}

Resources