2016-04-18 3 views
1

В моем проекте у меня есть несколько контроллеров представлений, которые являются подклассами UITableViewController, UIViewController, на каждом я хочу реализовать такое поведение:Использование расширения протокола, чтобы закрыть клавиатуру вне крана

Когда пользователь нажимает за пределами текста поле должно отклонить клавиатуру, которая была видна, когда пользователь входил в нее.

можно легко реализовать путем определения крана жест распознаватель и ассоциирования селектор, чтобы закрыть клавиатуру:

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

Поскольку я должен реализовать такое же поведение в нескольких контроллерах зрения, я пытаюсь определить способ избежать использования повторяющегося кода в нескольких классах.

Один из вариантов для меня - это определить BaseViewController, который является подклассом UIViewController, со всеми описанными выше методами, а затем подклассифицирует каждый из моих контроллеров представления на BaseViewController. Проблема с этим подходом заключается в том, что мне нужно определить два BaseViewController s один для UIViewController и один для UITableViewController, так как я использую подклассы обоих.

Другой вариант, который я пытаюсь использовать, - Protocol-Oriented Programming. Поэтому я определил протокол:

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

Затем определяется его расширение:

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

} 

На мой взгляд контроллера я подтвердил протокол:

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

Проблема есть - выше код разбивая с исключение:

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

Чтобы избежать этой аварии мне нужно переопределить hideKeyboard функции в пределах MyViewController класса, побеждающая свою цель избежать повторяющегося кода :(

Пожалуйста, предложить, если я делаю любую вещь неправильно здесь, и есть ли лучший способ реализовать мой требование.

+0

Кажется, что из MyViewController вы хотите повторно использовать функцию hideKeyboard в вашем BaseViewControllers. Но вы не объявили, что ваш MyViewController является подклассом BaseViewControllers. –

+0

Эй, спасибо за ваш ответ, но обратите внимание, что использование «BaseViewController» было первым вариантом, который я стараюсь избегать по причинам, указанным в моем опубликованном вопросе. В настоящее время я пытаюсь использовать «протокол-ориентированное программирование» вместо него :) – Devarshi

+0

Если вы используете протокол-ориентированное программирование, вам нужно реализовать hideKeyboard в MyViewController и, следовательно, иметь повторяющийся код. Протокол помогает вам просто определить «протокол», который должен быть согласован. –

ответ

3

Я думаю, что есть две возможные проблемы: литье Self в AnyObject, а не с использованием нового синтаксиса #selector.

Вместо приведения Self к AnyObject, определяют протокол в качестве протокола класса только:

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

Затем используйте ограничение типа, чтобы применить расширение только подклассов UIViewController, и использовать Self непосредственно в коде, вместо литья в AnyObject:

extension DismissKeyboardOnOutsideTap where Self: UIViewController { 

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

} 

Edit: Я вспомнил другую проблему, которую я столкнулся, делая это. Аргумент action для UITapGestureRecognizer является селектором Objective-C, но расширения Swift для классов не являются Objective-C.Поэтому я изменил протокол на протокол @objc, но это было проблемой, потому что в моем протоколе были некоторые опции Swift, а также были введены новые сбои, когда я пытался реализовать протокол в своем VC.

В конечном счете, я обнаружил альтернативный метод, который не требует селектора Objective-C в качестве аргумента; в моем случае я устанавливал наблюдателя центра NSNotification.

В вашем случае вам может быть лучше просто расширить UIViewController, так как UITableViewController является подклассом, а подклассы наследуют расширения (я думаю).

0

Как указал пользователь ConfusedByCode, хотя подход, ориентированный на протокол, начинается как хороший, он становится не-Swifty, поскольку компилятор заставляет вас использовать ключевое слово @objc.

Поэтому расширение UIViewController - лучший подход; По крайней мере, по моему мнению.

Для того, чтобы поддерживать чистую структуру проекта, создать файл с именем UIViewController+DismissKeyboard.swift и вставить следующее содержимое внутри:

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

Затем, любой один из контроллеров зрения или других базовых классов от Apple, наследуемых от UIViewController такого как UITableViewController, и т.д., если на то пошло, будет иметь доступ к методу configureKeyboardDismissOnSwipeDown().

Таким образом, простое обращение к configureKeyboardDismissOnSwipeDown() внутри viewDidLoad в каждом из ваших контроллеров будет автоматически вставлять жестом прокрутки вниз, чтобы закрыть клавиатуру.

Одно из предостережений - это то, что каждому контроллеру просмотра необходимо будет позвонить по телефону configureKeyboardDismissOnSwipeDown() отдельно. К сожалению, это облом, поскольку вы не можете просто переопределить viewDidLoad() в своем расширении. Более того, для меня все еще остается загадкой, почему Apple не реализовала это прямо на клавиатуре, так что нам разработчикам не нужно было бы кодировать ее.

В любом случае эта проблема может быть решена с помощью метода, называемого Method Swizzling. В принципе, это переопределяющие методы, данные Apple, так что их поведение меняется во время выполнения. Я больше не буду вдаваться в подробности о методе swizzling, не говоря о том, что очень опасно играть, так как вы будете модифицировать проверенный временем, надежный код, предоставленный Apple и используемый системой.

Впоследствии, когда вы реализуете приведенный выше метод dismissKeyboard() в контроллере вида, где вы хотите убрать клавиатуру, вы сможете это сделать.

 Смежные вопросы

  • Нет связанных вопросов^_^