Delegados en Swift?

132

¿Cómo se hace para hacer un delegado, es decir, NSUserNotificationCenterDelegateen forma rápida?

usuario3718173
fuente
44
¿Te refieres a implementar un delegado o definir tu propio delegado?
drewag

Respuestas:

72

No es tan diferente de obj-c. Primero, debe especificar el protocolo en su declaración de clase, como a continuación:

class MyClass: NSUserNotificationCenterDelegate

La implementación tendrá el siguiente aspecto:

// 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
}

Por supuesto, debe configurar el delegado. Por ejemplo:

NSUserNotificationCenter.defaultUserNotificationCenter().delegate = self;
Adán
fuente
1
¿Qué sucede cuando desea extender UIViewController, por ejemplo, en el objetivo-c, puede hacer que algo mienta esto @interface MyCustomClass: UIViewController <ClassIWantToUseDelegate>, permitiéndole iniciar / configurar el viewcontroller, así como llamar a los métodos de delegado en las subvistas? Algo similar a esto ?
Mahmud Ahmad
1
Hola Adam, pregunta rápida, ¿cómo puedo configurar delegate = self, si no puedo crear una instancia de un objeto porque es una clase genérica a la que no tengo acceso en la otra clase, pero quiero que la clase genérica llame a una función en la otra clase, de ahí la necesidad de delegar?
Marin
234

Aquí hay una pequeña ayuda sobre los delegados entre dos controladores de vista:

Paso 1: Haga un protocolo en el UIViewController que eliminará / enviará los datos.

protocol FooTwoViewControllerDelegate:class {
    func myVCDidFinish(_ controller: FooTwoViewController, text: String)
}

Paso 2: Declarar el delegado en la clase de envío (es decir, UIViewcontroller)

class FooTwoViewController: UIViewController {
    weak var delegate: FooTwoViewControllerDelegate?
    [snip...]
}

Paso 3: utilice el delegado en un método de clase para enviar los datos al método de recepción, que es cualquier método que adopte el protocolo.

@IBAction func saveColor(_ sender: UIBarButtonItem) {
        delegate?.myVCDidFinish(self, text: colorLabel.text) //assuming the delegate is assigned otherwise error
}

Paso 4: adopte el protocolo en la clase receptora

class ViewController: UIViewController, FooTwoViewControllerDelegate {

Paso 5: Implemente el método delegado

func myVCDidFinish(_ controller: FooTwoViewController, text: String) {
    colorLabel.text = "The Color is " +  text
    controller.navigationController.popViewController(animated: true)
}

Paso 6: configure el delegado en 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
    }
}

Y eso debería funcionar. Por supuesto, esto es solo fragmentos de código, pero debería darle la idea. Para una larga explicación de este código, puede ir a la entrada de mi blog aquí:

segues y delegados

Si está interesado en lo que sucede debajo del capó con un delegado, escribí sobre eso aquí:

debajo del capó con delegados

MakeAppPie
fuente
23
¿Step2 no debería haber una referencia débil para delegar? Si estoy en lo cierto, edítelo. Por cierto, puedes hacer que sea un valor opcional. Eso sería más rápido. delegado var débil: FooTwoViewControllerDelegate? PD: el delegado debe ser débil por el círculo de retención, el niño no debe mantener una fuerte referencia a los padres
Shial
1
A mi manera, cuando haga que el delegado sea opcional, resolverá su error de desempaquetado. delegate? .myVCDidFinish Porque si delegate no está configurado, el bacalao no se ejecutará ahora :) En su versión, intentará ejecutarse y no se desenvolverá si delegate es nulo y usted sí.
Shial
44
debe declarar un protocolo como este para hacer posible una referencia débil para delegar el protocolo FooTwoViewControllerDelegate: class {}
codingrhythm
¿Podría establecer en cada paso en qué VC es como VC1 y VC2? No estoy realmente seguro de dónde ponerlos.
Cing
2
@Shial: en realidad parece ser un poco complicado. weaksolo es necesario para clases, no estructuras y enumeraciones. Si el delegado va a ser una estructura o enumeración, entonces no debe preocuparse por los ciclos de retención. Sin embargo, el delegado es una clase (esto es cierto para muchos casos, ya que a menudo es un ViewController), entonces weaknecesita declarar su protocolo como una clase. Hay más información aquí stackoverflow.com/a/34566876/296446
Robert
94

Los delegados siempre me confundieron hasta que me di cuenta de que un delegado es solo una clase que hace algún trabajo para otra clase . Es como tener a alguien más allí para hacer todo el trabajo sucio por ti que no quieres hacer tú mismo.

Escribí una pequeña historia para ilustrar esto. Léelo en un patio de juegos si quieres.

Había una vez...

// 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.

En resumen, hay tres partes clave para hacer y usar el patrón delegado.

  1. El protocolo que define lo que el trabajador debe hacer
  2. la clase de jefe que tiene una variable delegada, que utiliza para decirle a la clase trabajadora qué hacer
  3. la clase trabajadora que adopta el protocolo y hace lo que se requiere

Vida real

En comparación con nuestra historia Bossy Big Brother anterior, los delegados se utilizan a menudo para las siguientes aplicaciones prácticas:

  1. Comunicación : una clase necesita enviar información a otra clase.
  2. Personalización : una clase quiere permitir que otra clase lo personalice.

La gran parte es que estas clases no necesitan saber nada de la otra con anterioridad, excepto que la clase delegada se ajusta al protocolo requerido.

Recomiendo leer los siguientes dos artículos. Me ayudaron a entender a los delegados incluso mejor que la documentación .

Una nota mas

Los delegados que hacen referencia a otras clases que no poseen deben usar la weakpalabra clave para evitar ciclos de referencia fuertes. Vea esta respuesta para más detalles.

Suragch
fuente
3
¡Finalmente alguien que puede explicar el protocolo y delegar con sentido común! ¡gracias hombre!
Engineeroholic
¿Qué sucede cuando Bossy Big Brother no sabe que es un hermano (genéricos)?
Marin
@ Marin, no estoy seguro de entender tu pregunta. A la lista de reglas (protocolo) no le importa quién llama para que se sigan las reglas o quién las sigue. Son solo reglas.
Suragch
Básicamente me refiero a mi pregunta, ligeramente simplificada por aquí. stackoverflow.com/questions/41195203/…
Marin
47

Recibí algunas correcciones para publicar en @MakeAppPie

En primer lugar, cuando crea un protocolo de delegado, debe cumplir con el protocolo de clase. Como en el ejemplo a continuación.

protocol ProtocolDelegate: class {
    func myMethod(controller:ViewController, text:String)
}

En segundo lugar, su delegado debe ser débil para evitar retener el ciclo.

class ViewController: UIViewController {
    weak var delegate: ProtocolDelegate?
}

Por último, está a salvo porque su protocolo es un valor opcional. Eso significa que su mensaje "nulo" no se enviará a esta propiedad. Es similar a la declaración condicional respondToselectoren objC pero aquí tiene todo en una línea:

if ([self.delegate respondsToSelector:@selector(myMethod:text:)]) {
    [self.delegate myMethod:self text:@"you Text"];
}

Arriba tienes un ejemplo de obj-C y debajo tienes un ejemplo Swift de cómo se ve.

delegate?.myMethod(self, text:"your Text")
Shial
fuente
estás seguro porque tu protocolo es un valor opcional ... porque usas el encadenamiento opcional delegate?.myMethodno se bloqueará porque si delegado es, nilentonces no pasaría nada. Sin embargo, si cometió un error y escribió delegate!.myMethod, podría bloquearse si no se establece un delegado, por lo que es básicamente una forma de estar seguro ...
Miel
33

Aquí hay una esencia que puse juntos. Me preguntaba lo mismo y esto ayudó a mejorar mi comprensión. Abre esto en un Xcode Playground para ver qué está pasando.

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()
SeeMeCode
fuente
Me gusta esto. Muy útil
Aspen
@SeeMeCode Hola, fue un buen ejemplo en primer lugar, pero todavía tengo un problema. ¿Cómo puedo hacer que mi UIViewControllerclase se ajuste al delegado que hicimos? ¿Deben declararse en un archivo rápido? Cualquier ayuda significará mucho.
Faruk
@Faruk Ha pasado un tiempo desde que publiqué esto, pero creo que lo que estás preguntando debería ser bastante simple (si estoy malentendido, me disculpo). Simplemente agregue el delegado a su UIViewController después de los dos puntos. Entonces algo como class ViewController : UIViewController NameOfDelegate.
SeeMeCode
@SeeMeCode sí, tienes mi pregunta bien. Intenté su sugerencia por cierto, pero cuando creo una clase de delegado de a.swiftacuerdo con su respuesta anterior, no aparece b.swift. No puedo llegar a ninguna clase fuera de mi archivo rápido. cualquier hardts?
Faruk
Una cosa que no entiendo es ¿por qué debería crear una nueva instancia de YelpApi solo para llamar al delegado de YelpApi? ¿Qué sucede si la instancia que se está ejecutando es diferente de la 'nueva' que acabo de crear ... cómo sabe qué delegado pertenece a qué instancia de YelpApi?
Marin
15

DELEGADOS EN SWIFT 2

Estoy explicando con un ejemplo de Delegado con dos viewControllers. En este caso, SecondVC Object está enviando datos de vuelta al primer View Controller.

Clase con declaración de protocolo

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("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)
    }

}

En First ViewController Protocol, la conformidad se realiza aquí:

class ViewController: UIViewController, getDataDelegate

Definición del método de protocolo en First View Controller (ViewController)

func getDataFromAnotherVC(temp : String)
{
  // dataString from SecondVC
   lblForData.text = dataString
}

Durante la inserción de SecondVC desde First View Controller (ViewController)

let objectPush = SecondVC()
objectPush.delegateCustom = self
self.navigationController.pushViewController(objectPush, animated: true)
Maninderjit Singh
fuente
Sus últimas 3 líneas me ayudaron a comprender mi escenario y resolvieron mi problema. ¡Gracias hombre! :)
iHarshil
6

Primera clase:

protocol NetworkServiceDelegate: class {

    func didCompleteRequest(result: String)
}


class NetworkService: NSObject {

    weak var delegate: NetworkServiceDelegate?

    func fetchDataFromURL(url : String) {
        delegate?.didCompleteRequest(url)
    }
}

Segunda clase:

class ViewController: UIViewController, NetworkServiceDelegate {

    let network = NetworkService()

    override func viewDidLoad() {
        super.viewDidLoad()
        network.delegate = self
        network.fetchDataFromURL("Success!")
    }



    func didCompleteRequest(result: String) {
        print(result)
    }


}
Ekambaram E
fuente
al compilar el código anterior, muestra un error, por Type 'ViewController' does not conform to protocol 'NetworkServiceDelegate'favor, sugiéralo. Es mi sexto día en Swift :)
Vaibhav Saran
4

Muy fácil paso a paso (100% de trabajo y probado)

Paso 1: Crear método en el primer controlador de vista

 func updateProcessStatus(isCompleted : Bool){
    if isCompleted{
        self.labelStatus.text = "Process is completed"
    }else{
        self.labelStatus.text = "Process is in progress"
    }
}

Paso 2: establecer delegado mientras se empuja al segundo controlador de vista

@IBAction func buttonAction(_ sender: Any) {

    let secondViewController = self.storyboard?.instantiateViewController(withIdentifier: "secondViewController") as! secondViewController
    secondViewController.delegate = self
    self.navigationController?.pushViewController(secondViewController, animated: true)
}

paso 3: establecer delegado como

clase ViewController: UIViewController, ProcessStatusDelegate {

Paso 4: Crear protocolo

protocol ProcessStatusDelegate:NSObjectProtocol{
func updateProcessStatus(isCompleted : Bool)
}

paso5: toma una variable

var delegate:ProcessStatusDelegate?

Paso 6: Mientras regresa al método de delegado de llamadas del controlador de vista anterior, el primer controlador de vista notifica con datos

@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)

}
Sr. Javed Multani
fuente
3

Ejemplo simple:

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
Poli
fuente
¿Por qué utiliza la palabra clave "clase" en la descripción del protocolo? ¿Cuál es la diferencia entre usarlo y no usarlo?
Vlad
2
La palabra clave class significa que es un protocolo de clase solamente. Puede limitar la adopción del protocolo a tipos de clase, y no a estructuras o enumeraciones, agregando la palabra clave class. Probablemente no debería haberlo agregado para evitar confusiones, pero como me lo pediste, lo guardaré.
Bobby
2

Los delegados son un patrón de diseño que permite que un objeto envíe mensajes a otro objeto cuando ocurre un evento específico. Imagine que un objeto A llama a un objeto B para realizar una acción. Una vez que se completa la acción, el objeto A debe saber que B ha completado la tarea y tomar las medidas necesarias, ¡esto se puede lograr con la ayuda de los delegados! Aquí hay un tutorial que implementa delegados paso a paso en Swift 3

Enlace tutorial

James Rochabrun
fuente
0

Las soluciones anteriores parecían un poco acopladas y, al mismo tiempo, evitan reutilizar el mismo protocolo en otros controladores, es por eso que he venido con la solución que es más tipada con un borrado de tipo genérico.

@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")    

salida : tiene un nuevo valor newValue

Jans
fuente
Estoy interesado en aprender más sobre esto. ¿Puede explicar más acerca de los términos que utiliza: acoplado, "evitar reutilizar el mismo protocolo", "borrado de tipo genérico". ¿Por qué es importante abstraerlo así? ¿Se debe hacer siempre esto?
Suragch
0

En swift 4.0

Cree un delegado en la clase que necesite enviar algunos datos o proporcionar alguna funcionalidad a otras clases

Me gusta

protocol GetGameStatus {
    var score: score { get }
    func getPlayerDetails()
}

Después de eso en la clase que va a confirmar a este delegado

class SnakesAndLadders: GetGameStatus {
    func getPlayerDetails() {

 }
}
Saranjith
fuente