2016-04-18 7 views
1

In meinem Projekt entlassen ich einige View-Controller haben, die Unterklassen von UITableViewController sind, UIViewController, auf jedes ich dieses Verhalten implementieren möchten:Protokollerweiterung Mit Tastatur auf außerhalb tippen

Wenn Benutzer außerhalb eines Textes tippt Feld sollte es die Tastatur zu entlassen, die sichtbar war, wenn der Benutzer in es klopfte.

Ich kann es leicht implementieren, indem eine Anzapfung Gestenerkenner definiert und einen Selektor Zuordnen der Tastatur zu entlassen:

class MyViewController { 

    override func viewDidLoad() { 
     super.viewDidLoad() 
     configureToDismissKeyboard() 
    } 

    private func configureToDismissKeyboard() { 
     let tapGesture = UITapGestureRecognizer(target: self, action: "hideKeyboard") 
     tapGesture.cancelsTouchesInView = true 
     form.addGestureRecognizer(tapGesture) 
    } 

    func hideKeyboard() { 
     form.endEditing(true) 
    } 
} 

Da ich gleiche Verhalten in mehreren View-Controller zu implementieren haben, versuche ich zu identifizieren eine Möglichkeit, sich wiederholenden Code in mehreren Klassen zu vermeiden.

Eine Option für mich ist eine BaseViewController zu definieren, die eine Unterklasse von UIViewController ist, mit allen oben genannten Methoden definiert und dann jede meiner View-Controller Unterklasse BaseViewController. Das Problem mit diesem Ansatz ist, dass ich zwei BaseViewController s eins für UIViewController und eins für UITableViewController definieren muss, da ich Unterklassen von beiden verwende.

Die andere Option, die ich versuche zu verwenden, ist - Protocol-Oriented Programming. So I definiert ein Protokoll:

protocol DismissKeyboardOnOutsideTap { 
    var backgroundView: UIView! { get } 
    func configureToDismissKeyboard() 
    func hideKeyboard() 
} 

Dann seine Erweiterung definiert:

extension DismissKeyboardOnOutsideTap { 
    func configureToDismissKeyboard() { 
     if let this = self as? AnyObject { 
      let tapGesture = UITapGestureRecognizer(target: this, action: "hideKeyboard") 
      tapGesture.cancelsTouchesInView = true 
      backgroundView.addGestureRecognizer(tapGesture) 
     } 

    } 

    func hideKeyboard() { 
     backgroundView.endEditing(true) 
    } 

} 

Meiner Ansicht Controller ich dem Protokoll bestätigt:

class MyViewController: UITableViewController, DismissKeyboardOnOutsideTap { 

    var backgroundView: UIView! 

    override func viewDidLoad() { 
     super.viewDidLoad() 

     // configuring background view to dismiss keyboard on outside tap 
     backgroundView = self.tableView 
     configureToDismissKeyboard() 
    } 
} 

Problem ist - oben Code stürzt mit Ausnahme:

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[MyProject.MyViewController hideKeyboard]: unrecognized selector sent to instance 0x7f88c1e5d700' 

diesen Absturz zu vermeiden ich hideKeyboard Funktion innerhalb MyViewController Klasse neu definieren muß, was meinen Zweck der Vermeidung repetitiven Code besiegt :(

Bitte legen nahe, wenn ich falsch hier irgend etwas tue, oder gibt es eine bessere Möglichkeit, meine zu implementieren Anforderung.

+0

Es scheint so, als ob Sie von MyViewController aus die Funktion hideKeyboard in Ihren BaseViewControllern wiederverwenden möchten. Aber Sie haben Ihren MyViewController nicht als Unterklasse von BaseViewControllern deklariert. –

+0

Hey vielen Dank für Ihre Antwort, aber bitte beachten Sie, dass die Verwendung von 'BaseViewController' die erste Option war, die ich aus Gründen vermeiden möchte, die in meiner geposteten Frage angegeben sind. Momentan versuche ich 'protokollorientierte Programmierung' zu verwenden :) – Devarshi

+0

Wenn Sie eine protokollorientierte Programmierung verwenden, müssen Sie das hideKeyboard in Ihrem MyViewController implementieren und sich wiederholenden Code haben. Das Protokoll hilft Ihnen, nur das zu formulierende "Protokoll" zu definieren. –

Antwort

3

Ich denke, es gibt zwei mögliche Probleme: Gießen Self zu AnyObject, und nicht mit der neuen #selector Syntax.

Statt Self-AnyObject von Gießen, definieren das Protokoll als eine Klasse-only-Protokoll:

protocol DismissKeyboardOnOutsideTap: class { 
    // protocol definitions... 
} 

Dann Art Einschränkungen verwenden, um Ihre Erweiterung nur Subklassen von UIViewController gelten, und verwenden Sie Self direkt im Code, Gießen statt auf AnyObject:

extension DismissKeyboardOnOutsideTap where Self: UIViewController { 

    func configureToDismissKeyboard() { 
     let gesture = UITapGestureRecognizer(target: self, 
      action: #selector(Self.hideKeyboard())) 
     gesture.cancelsTouchesInView = true 
     backgroundView.addGestureRecognizer(gesture) 
    } 

} 

Edit: ich das andere Problem erinnerte ich lief in, wenn dies zu tun. Das Argument action für UITapGestureRecognizer ist ein Objective-C-Selektor, aber Swift-Erweiterungen für Klassen sind nicht Objective-C.Also änderte ich das Protokoll in ein @objc Protokoll, aber das war ein Problem, weil mein Protokoll einige Swift-Optionen enthielt, und es führte auch neue Abstürze ein, als ich versuchte, das Protokoll in meinem VC zu implementieren.

Letztendlich entdeckte ich eine alternative Methode, die keinen Objective-C-Selektor als Argument erforderte; In meinem Fall habe ich einen NSNotification Center-Beobachter eingestellt.

In Ihrem Fall könnten Sie besser dran einfach UIViewController erweitern, wie UITableViewController ist eine Unterklasse, und Unterklassen erben Erweiterungen (glaube ich).

0

Wie vom Benutzer ConfusedByCode hingewiesen, obwohl ein Protokoll-orientierten Ansatz beginnt als eine nette, wird es un-Swifty, wie der Compiler zwingt Sie, das Schlüsselwort @objc zu verwenden.

Daher ist die Erweiterung UIViewController ein besserer Ansatz; zumindest meiner Meinung nach.

Um eine saubere Projektstruktur zu erhalten, erstellen Sie eine Datei UIViewController+DismissKeyboard.swift Namen und fügen Sie den folgenden Inhalt innen:

import UIKit 

extension UIViewController { 
    func configureKeyboardDismissOnTap() { 
    let keyboardDismissGesture = UITapGestureRecognizer(target: self, 
                               action: #selector(self.dismissKeyboard)) 

    view.addGestureRecognizer(keyboardDismissGesture) 
    } 

    func dismissKeyboard() { 
    // to be implemented inside your view controller(s) wanting to be able to dismiss the keyboard via tap gesture 
    } 
} 

Danach kann jede Ihrer Ansicht-Controller oder andere Basisklassen von Apple aus UIViewController vererben so als UITableViewController, etc., wird Zugang zu der Methode configureKeyboardDismissOnSwipeDown() haben.

Daher ruft nur configureKeyboardDismissOnSwipeDown() innerhalb viewDidLoad in jedem Ihrer Ansicht Controller automatisch eine Wischgeste nach unten, um die Tastatur zu schließen.

Ein noch verbleibender Nachteil ist, dass jeder View Controller configureKeyboardDismissOnSwipeDown() separat aufrufen muss. Leider ist dies ein Mist, da Sie viewDidLoad() in Ihrer Erweiterung nicht einfach außer Kraft setzen können. Darüber hinaus ist es für mich immer noch ein Rätsel, warum Apple dies nicht direkt in die Tastatur implementiert hat, so dass wir Entwickler es nicht programmieren müssten.

Wie auch immer, dieses Problem kann durch eine Technik namens Method Swizzling gelöst werden. Im Grunde sind es die Methoden, die von Apple vorgegeben werden, damit sich ihr Verhalten zur Laufzeit ändert. Ich werde nicht näher auf Methodenwechseln eingehen, als zu sagen, dass es sehr gefährlich sein kann, herumzuspielen, da man den kampferprobten, soliden Code modifizieren würde, der von Apple bereitgestellt und vom System verwendet wird.

Anschließend, wenn Sie die oben angegebene dismissKeyboard() Methode in einem View-Controller implementieren, wo Sie in der Lage sein, die Tastatur zu entlassen, können Sie dies tun.