2015-04-11 5 views
6

Ich bin ein Swift-Projekt erstellen, und ich möchte ein bestimmtes Protokoll definieren, die anderen Komponenten erzwingt eine animate Methode zu implementieren:Was wäre eine bessere Strategie für IBActions in Swift-Protokollen?

protocol AnimatableBehavior { 
    @IBAction func animate() 
} 

Das Problem ist, möchte ich diese Methode ein IBAction sein, aber ich habe diesen Fehler von XCode:

Nur Instanzmethoden deklariert

Meine Frage 'IBAction' werden soll, wie woul Solltest du so etwas umsetzen?

Ich habe in Betracht gezogen:

  1. @IBAction entfernen, aber dann muss ich erinnere mich, es in jeder Klasse hinzufügen, die implementiert. Nicht sehr elegant und fehleranfällig.
  2. Erstellen Sie eine Basisklasse anstelle von Protokoll, aber dann erzwinge ich alle Komponenten zu Unterklasse meiner Basisklasse statt ihrer eigenen, also ist es keine gültige Option.

Irgendwelche anderen Ideen?


EDIT: Antwort auf Kommentare unten.

Die Idee des IBAction auf dem Protokoll ist, weil im Projekt wird es viele verschiedene Entwickler geben, die kleine UI-Komponenten implementieren, von denen alle die animierte Methode haben. Die Komponenten können programmatisch oder von Interface Builder hinzugefügt werden und es ist sehr praktisch, dass sie immer IBAction sind, weil ich plane, sie aus IB-Dateien zu zu kompilieren, um die View Controller im maximalen Umfang zu vereinfachen (und dies ist eindeutig nur View Aufgabe).

Daher ist die unten vorgeschlagene Lösung des Hinzufügens einer Methode in der Steuerung, die animate der Komponente aufruft, nicht gut, weil es redundanter Code ist und Ihren Controller abhängiger von Ihrer Ansicht macht.

Die Idee, den Entwickler das Hinzufügen des IBAction Schlüsselwortes auf der Methode zu lassen, ist praktikabel, aber wie gesagt, es ist fehleranfällig (und damit meine ich, dass es einige vergessen wird), und ich will es stellen Sie sicher, dass dies immer von IB aus zugänglich ist. Es fügt auch zusätzliche kognitive Belastung hinzu, weil ich diesen Mangel an IBAction auf dem Protokoll dokumentieren muss und den Implementierer auffordern muss, es manuell hinzuzufügen.

Ich weiß, ist nicht die übliche Art der Arbeit in iOS und UIKit, aber das war der Grund, warum ich die Frage gestellt, vielleicht hat jemand eine alternative Idee.

+0

Ich glaube nicht, dass es eine andere Lösung dafür gibt. Vielleicht schau dir das große Bild an, was versuchst du damit zu erreichen? – Rengers

+0

Ich sehe nicht, wie die tatsächlichen Implementierer feststellen können, ob "animate()" eine '@ IBAction' ist oder nicht. Bitte lesen Sie meine Antwort und lassen Sie mich wissen, wenn Sie Fragen haben. Ich denke, das kann ein XY-Problem sein, aber eine Methode in einem Protokoll zu machen, kann unmöglich einen Sinn ergeben, wenn Sie wirklich verstehen, was '@ IBAction' ist und tut. – nhgrif

+0

Ich habe Ihren Kommentar gelesen und ich verstehe, was 'IBAction' tut, aber es macht immer noch Sinn. Sehen Sie meine Bearbeitung :) –

Antwort

9

Es macht keine Sinn, eine @IBAction in einem Protokoll zu haben. @IBAction ist nichts anderes als ein Schlüsselwort für den Interface Builder, um einen Haken zu haben, wenn Sie die Kontrolle und das Ziehen von Interface Builder auf Ihren tatsächlichen Quellcode haben.

Dies ist nur ein einfaches Missverständnis, was @IBAction tatsächlich ist und tut.

  1. Verfahren muss nicht als @IBAction um markiert werden, damit er das Ziel eines UI-Elements Aktionen sein. Sie verknüpfen programmgesteuert jede Methode mit einer beliebigen Aktion, indem Sie die addTarget Menge von Methoden verwenden, die UI-Elemente haben. Die Methode muss dazu nicht als @IBAction markiert sein.

  2. Unabhängig davon, ob oder ob nicht ein Protokoll, ein Verfahren, wie @IBAction definiert, entspricht, die Klasse, zu dem Protokoll hinzufügen kann (und immer noch das Protokoll konform sein.

    protocol FooProtocol { 
        func doSomething() 
    } 
    
    class ViewControllerA: UIViewController, FooProtocol { 
        @IBAction func doSomething() { 
         // do something 
        } 
    } 
    
    class ViewControllerB: UIViewController, FooProtocol { 
        func doSomething() { 
         // do something 
        } 
    } 
    

    Beide dieser View-Controller-Subklassen konform das Protokoll, und mit @IBAction ist es NUR notwendig, wenn Sie eine Aktion von Interface Builder zu anschließen beabsichtigen!


Schließlich, was auch immer Sie versuchen zu tun, wenn Sie denken, dass ein @IBAction in Ihrem Protokoll notwendig ist, denke ich, dass Sie den falschen Ansatz zu etwas nehmen. Es ist schwer zu sagen, was der richtige Ansatz wäre, ohne mehr Details darüber zu wissen, was Sie tatsächlich tun, aber es macht keinen Sinn, dass @IBAction zu einem Protokoll gehört.

Für mich scheint es, als ob die Methoden, die Ihr Protokoll erzwingt, überhaupt nicht an @IBAction-Methoden gebunden sein sollten. Stattdessen sollte die Benutzerinteraktion, die die Animation auslösen sollte, wiederum die Methode animate aufrufen. Zum Beispiel, wenn wir nicht über das Protokoll zu sprechen, wäre meine Empfehlung diese Art aufgebaut sein:

class ViewController: UIViewController { 
    @IBAction func buttonThatStartsAnimation { 
     self.animate() 
    } 

    func animate { 
     // code that does all the animation 
    } 
} 

Also, mit dem Protokoll, sollten wir die gleiche Trennung der Aufgaben zwischen dem Verfahren nehmen, das tatsächlich ist die Einleitung der Animationscode (was im Fall von Protokollen offensichtlich eine andere äußere Klasse ist), und die animate Methode sollte nur die relevanten Animationen behandeln.

Wichtig, nur als eine allgemeine Regel, sollten Sie nicht direkt auf Ihre @IBAction Methoden oder Ihre @IBOutlet Variablen direkt von außerhalb der Klasse beziehen, die sie definiert.

+2

Vielen Dank für die Zeit, die Sie für die Beantwortung verbracht haben. Ich muss jedoch einigen Ihrer Kommentare nicht zustimmen. Mir ist völlig klar, was "IBAction" bedeutet, aber das ändert nichts an der Frage. Überprüfen Sie meine Bearbeitung. –

+0

BTW, Sie sagten "was auch immer Benutzerinteraktion sollte die Animation auslösen, sollte wiederum die animierte Methode aufrufen.". Das ist genau das, was ich will, aber indem Sie "that leim" in der VC machen, gehen Sie tatsächlich von Ihrer View-Ebene zu Ihrer Controller-Ebene und zurück zur View-Ebene, wenn Sie alles in der View-Ebene in IB behalten könnten. wo es sein sollte, da es nur eine UI-Änderung ist). View-Controller werden in Cocoa immer missbraucht. Dies ist ein klares Beispiel für IMO. –

+2

Bei allem Respekt, ich stimme überhaupt nicht zu. Das Deklarieren einiger Features in einem Protokoll ist durchaus sinnvoll. Zum Beispiel kann die Hälfte der "ViewControllers" in Ihrer App eine Funktion haben, um den Benutzerprofil-Controller an mehreren verschiedenen Stellen zu präsentieren, und es könnte sehr nützlich sein, ein '@IBAction func actionPresentProfile (sender:)' in einem Protokoll zu verwenden. –

0

Ich stimme völlig mit OP überein, obwohl bis Swift 3.1 Sie wirklich nichts als @IBOutlet, @IBAction, @objc usw. in einem Protokoll deklarieren können. Als Abhilfe können, wählte ich etwas auf pod 'ActionKit' Basis zu bauen und schrieb so etwas wie:

protocol RequiresAnimation { 
    var animateButton: UIButton! { get } 
    func enableAnimateButton() 
    func actionAnimate() 
} 

extension RequiresAnimation where Self: UIViewController { 
    func enableAnimateButton() { 
     animateButton.addControlEvent(.touchUpInside) { 
      self.actionAnimate() 
     } 
    } 
    func actionAnimate() { 
     // animate here 
    } 
} 

Und Ihre View-Controller machen:

class MyViewController: UIViewController, RequiresAnimation { 
    @IBOutlet var animateButton: UIButton! 
    override func viewDidLoad() { 
     super.viewDidLoad() 
     enableAnimateButton() 
    } 
} 

Ich wünschte, es wäre einfacher Ansatz, aber so weit Sie Machen Sie diese 2 Dinge manuell: Erklären Sie Ihre Taste als @IBOutlet und rufen Sie eine Setup-Funktion auf. Der Grund, warum wir import ActionKit brauchen, ist, dass wir addTarget in Protokollerweiterung nicht können.

+0

Ist dies eigentlich bevorzugt, anstatt einfach die Steckdose für den Knopf zu machen und statt dessen den Knopf zu einer Aktion in Ihrem View-Controller zu hängen, und in dieser Aktion einfach die 'actionAnimate'-Methode aus dem Protokoll aufrufen? Ich sehe nicht, wie dieser Ansatz bevorzugt wird. – nhgrif

+0

[So ...] (https://gist.github.com/nhgrif/acc5fa8d27f89677c263919436991dc8) – nhgrif

+0

Nichts ist immer besser, es hängt von Ihrem Anwendungsfall ab. Ihr Ansatz ist zwar flexibler, aber in meinem Fall möchte ich festlegen, dass MyViewController "eine Schaltfläche haben soll, die nur eine bestimmte Animation ausführt". Ihr Ansatz führt zu einer versteckten Abhängigkeit. Wenn Sie beispielsweise einen anderen View-Controller präsentieren, wissen Sie nicht, ob vor oder nach dem Aufruf von animate alles abgebrochen wird. Auf der anderen Seite, wenn Sie etwas vor oder nach dem 'animieren' wollen, wäre mein Ansatz schwer zu erweitern. –