Instantly share code, notes, and snippets.
Last active
June 1, 2021 01:20
-
Save gngrwzrd/f5a868844e22d0a2992138dac8e2137c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extension Notification.Name { | |
///Post this to go next. | |
static let FormFieldNavigationNext = Notification.Name("FormFieldNavigationNext") | |
///Post this to go previous. | |
static let FormFieldNavigationPrevious = Notification.Name("FormFieldNavigationPrevious") | |
///Post this to end editing. | |
static let FormFieldNavigationDone = Notification.Name("FormFieldNavigationDone") | |
} | |
class FormFieldNavigator { | |
weak var view:UIView? { | |
didSet { | |
if let _ = view { | |
update() | |
} else { | |
inputs = [] | |
} | |
} | |
} | |
weak var firstResponder:UIView? { | |
get { | |
return _firstResponder() | |
} set { | |
_firstResponderStorage = newValue | |
_firstResponderStorage?.becomeFirstResponder() | |
} | |
} | |
private var _firstResponderStorage:UIView? | |
///All inputs found. | |
private var inputs:[UIView] = [] | |
init(view:UIView? = nil) { | |
NotificationCenter.default.addObserver(self, selector: #selector(goNext(notification:)), name: .BLFormFieldNavigationNext, object: nil) | |
NotificationCenter.default.addObserver(self, selector: #selector(goPrevious(notification:)), name: .BLFormFieldNavigationPrevious, object: nil) | |
NotificationCenter.default.addObserver(self, selector: #selector(onDone(notification:)), name: .BLFormFieldNavigationDone, object: nil) | |
self.view = view | |
} | |
private func allInputs(view:UIView) -> [UIView] { | |
var inputs:[UIView] = [] | |
for view in view.subviews { | |
if view is UITextField || view is UITextView { | |
inputs.append(view) | |
} | |
let subInputs = allInputs(view: view) | |
if subInputs.count > 0 { | |
inputs.append(contentsOf: subInputs) | |
} | |
} | |
return inputs | |
} | |
func update() { | |
guard let view = view else { return } | |
inputs = allInputs(view: view) | |
inputs.sort { view1, view2 in | |
let view1Frame = view1.convert(CGPoint.zero, to: nil) | |
let view2Frame = view2.convert(CGPoint.zero, to: nil) | |
if view1Frame.y == view2Frame.y { | |
return view1Frame.x < view2Frame.x | |
} | |
return view1Frame.y < view2Frame.y | |
} | |
} | |
private func nextResponderIndex(_ responder:UIView) -> Int? { | |
if let idx = indexOfResponder(responder) { | |
let next = idx + 1 | |
if next < responders.count { | |
return next | |
} | |
} | |
return nil | |
} | |
private func prevResponderIndex(_ responder:UIView) -> Int? { | |
if let idx = indexOfResponder(responder), idx > 0 { | |
return idx - 1 | |
} | |
return nil | |
} | |
private func nextResponder(from responder:UIView) -> UIView? { | |
if let nextIndex = nextResponderIndex(responder) { | |
return responders[nextIndex] | |
} | |
return nil | |
} | |
private func previousResponder(from responder:UIView) -> UIView? { | |
if let prevIndex = prevResponderIndex(responder) { | |
return responders[prevIndex] | |
} | |
return nil | |
} | |
private func _firstResponder() -> UIView? { | |
if let firstResponder = responders.filter({ $0.isFirstResponder }).first { | |
return firstResponder | |
} | |
return nil | |
} | |
private func resignFirstResponder() { | |
guard let first = _firstResponder() else { return } | |
first.endEditing(true) | |
} | |
@objc private func goNext(notification:Notification) { | |
guard let first = _firstResponder() else { return } | |
goNext(view: first) | |
} | |
private func goNext(view:UIView) { | |
if let next = nextResponder(from: view) { | |
if next.isHiddenInSuperChain() { | |
goNext(view: next) | |
} else { | |
next.becomeFirstResponder() | |
} | |
} else { | |
resignFirstResponder() | |
} | |
} | |
@objc private func goPrevious(notification:Notification) { | |
guard let first = _firstResponder() else { return } | |
goPrev(view: first) | |
} | |
private func goPrev(view:UIView) { | |
if let prev = previousResponder(from: view) { | |
if prev.isHiddenInSuperChain() { | |
goPrev(view: view) | |
} else { | |
prev.becomeFirstResponder() | |
} | |
} else { | |
resignFirstResponder() | |
} | |
} | |
@objc private func onDone(notification:Notification) { | |
guard let first = _firstResponder() else { return } | |
first.endEditing(true) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment