Swift is Protocol-Oriented Programming, and it's more powerful by default implementations of extensions of protocols. You can mixin methods to classes like Ruby's Mixin.
However, iOS as a UI framework, objects like UIViewController have their own life cyle, if you can't listen life cyle methods, extensions as mixin don't really help.
For example, I write a protocol with extension to listen keyboard events
protocol KeyboardMixin {
var keyboardHeight: CGFloat? { set get }
func registerKeyboard()
func deregisterKeyboard()
}
extension KeyboardMixin {
func registerKeyboard() {
NotificationCenter.default.addObserver(forName: NSNotification.Name.UIKeyboardWillHide, object: nil, queue: nil) { [weak self] notification in
self?.keyboardHeight = nil
}
NotificationCenter.default.addObserver(forName: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil, queue: nil) { [weak self] notification in
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
self?.keyboardHeight = keyboardSize.height
}
}
}
func deregisterKeyboard() {
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillHide, object: nil)
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
}
}
But I still need to register and deregister by myself
class ViewController: UIViewController, KeyboardMixin {
var keyboardHeight: CGFloat? {
didSet { }
}
override func viewWillAppear(_ animated: Bool) {
registerKeyboard()
}
override func viewWillDisappear(_ animated: Bool) {
deregisterKeyboard()
}
}
The problem is why can't I mixin something to existing methods e.g. UIViewController life cyle?
If you have programmed React.js, you'll find its Mixin mechanism is very useful. So I just copy the idea to iOS. After using this package, you can write a mixin like this.
public protocol KeyboardMixin: ViewControllerMixinable {
var keyboardHeight: CGFloat? { set get }
}
public extension KeyboardMixin {
private func registerKeyboard() {
NotificationCenter.default.addObserver(forName: NSNotification.Name.UIKeyboardWillHide, object: nil, queue: nil) { [weak self] notification in
self?.keyboardHeight = nil
}
NotificationCenter.default.addObserver(forName: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil, queue: nil) { [weak self] notification in
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
self?.keyboardHeight = keyboardSize.height
}
}
}
private func deregisterKeyboard() {
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillHide, object: nil)
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
}
fileprivate func viewWillAppear(_ animated: Bool) {
registerKeyboard()
}
fileprivate func viewWillDisappear(_ animated: Bool) {
deregisterKeyboard()
}
}
And use it like below
class ViewController: UIViewController, KeyboardMixin {
var keyboardHeight: CGFloat? {
didSet { }
}
override func viewWillAppear(_ animated: Bool) {
// Don't worry, you can still do things here...
}
}
It can't be simpler!
This package uses iOS runtime to swizzle methods, so all override methods and mixins' methods will be called simultaneously.
This package support mixin to
- UIViewControllerLifeCycle
- ExtendTableViewDelegate
- UIScrollViewDelegate
- UITextFieldDelegate
Check out Example ViewController, it shows how amazing to use Mixin
Only tested in Swift 4
Mixin is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod 'Mixin'
Howard Yang, appdevoney@gmail.com
Mixin is available under the MIT license. See the LICENSE file for more info.