Upgrade to Pro — share decks privately, control downloads, hide ads and more …

RxSwiftを導入したくても、できなかったら?

 RxSwiftを導入したくても、できなかったら?

Taiki Suzuki

March 01, 2017
Tweet

More Decks by Taiki Suzuki

Other Decks in Programming

Transcript

  1. UISearchBar + Timer class ViewController: UIViewController, UISearchBarDelegate { @IBOutlet weak

    var searchBar: UISearchBar! private lazy var timer: Timer = { return self.createTimer() }() override func viewDidLoad() { super.viewDidLoad() searchBar.delegate = self } func createTimer() -> Timer { return Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(ViewController.handleTimer(_:)), userInfo: nil, repeats: false) } func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { timer.invalidate() timer = createTimer() } @objc private func handleTimer(_ timer: Timer) { print(self.searchBar.text) } }
  2. UISearchBar + DispatchQueue class ViewController: UIViewController, UISearchBarDelegate { @IBOutlet weak

    var searchBar: UISearchBar! //0.5ඵ͝ͱʹॲཧΛglobalQueueͰߦ͏ClosureΛPropertyͱͯ͠อ࣋ let debounce = DispatchQueue.global().debounce(delay: .milliseconds(500)) override func viewDidLoad() { super.viewDidLoad() searchBar.delegate = self } func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { debounce { print(searchBar.text) } } }
  3. Implementation extension DispatchQueue { func debounce(delay: DispatchTimeInterval) -> (_ action:

    @escaping () -> ()) -> () { var lastFireTime: DispatchTime = .now() return { action in let deadline: DispatchTime = .now() + delay lastFireTime = .now() self.asyncAfter(deadline: deadline) { let now: DispatchTime = .now() let when: DispatchTime = lastFireTime + delay if now < when { return } lastFireTime = .now() action() } } } }
  4. NotificationCenter + RxSwift class ViewController: UIViewController { //some properties private

    var disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.rx.notification(.UIKeyboardWillShow).asDriver(onErrorDriveWith: .empty()) .flatMap { notification -> Driver<UIKeyboardNotificationInfo> in guard let keyboardInfo = UIKeyboardNotificationInfo(from: notification) else { return Driver.empty() } return Driver.just(keyboardInfo) } .drive(onNext: { [unowned self] info in self.keyboardWillAnimate(toDefault: false, with: info) }) .addDisposableTo(disposeBag) } func keyboardWillAnimate(toDefault: Bool, with info: UIKeyboardNotificationInfo) { //Keyboard animation } }
  5. NoticeObserveKit https://github.com/marty-suzuki/NoticeObserveKit class ViewController: UIViewController { //some properties private var

    pool = NoticeObserverPool() override func viewDidLoad() { super.viewDidLoad() UIKeyboardWillShow.observe { [unowned self] in self.keyboardWillAnimate(toDefault: false, with: $0) }.addObserverTo(pool) } }
  6. NoticeObserveKit.NoticeType struct TalkObjectDidUpdate: NoticeType { typealias InfoType = TalkObject static

    let name: Notification.Name = Notification.Name("TalkObjectDidUpdate") } let talk: TalkObject = //... TalkObjectDidUpdate.post(info: talk)
  7. RxPagination https://github.com/tryswift/RxPagination extension Reactive where Base: UIScrollView { var reachedBottom:

    ControlEvent<Void> { let observable = contentOffset .flatMap { [weak base] contentOffset -> Observable<Void> in guard let scrollView = base else { return Observable.empty() } let visibleHeight = scrollView.frame.height - scrollView.contentInset.top - scrollView.contentInset.bottom let y = contentOffset.y + scrollView.contentInset.top let threshold = max(0.0, scrollView.contentSize.height - visibleHeight) return y > threshold ? Observable.just() : Observable.empty() } return ControlEvent(events: observable) } }
  8. Paging + UITableViewDelegate class ViewController: UIViewController, UITableViewDelegate { @IBOutlet weak

    var tableView: UITableView! private var reachedBottom: Bool = false { didSet { if reachedBottom == oldValue { return } guard reachedBottom else { return } didReachBottom() } } override func viewDidLoad() { super.viewDidLoad() tableView.delegate = self } private func didReachBottom() { print("scrollViewDidReachBottom!") } func scrollViewDidScroll(_ scrollView: UIScrollView) { let maxDistance = max(0, scrollView.contentSize.height - scrollView.bounds.size.height) reachedBottom = maxDistance < scrollView.contentOffset.y } }
  9. Paging + ReachExtension class ViewController: UIViewController { @IBOutlet weak var

    tableView: UITableView! override func viewDidLoad() { super.viewDidLoad() tableView.re.delegate = self tableView.re.scrollViewDidReachBottom = { scrollView in print("scrollViewDidReachBottom!") } } } extension ViewController: UITableViewDelegate { func scrollViewDidScroll(_ scrollView: UIScrollView) { print("scrollViewDidScroll = \(scrollView.contentOffset.y)") } }
  10. DelegateTransporter.m @interface DelegateTransporter () @property (nonnull, nonatomic, strong) NSHashTable<NSObject *>

    *delegates; @end @implementation DelegateTransporter - (instancetype)initWithDelegates:(NSArray<id> *)delegates { self = [super init]; if (self != nil) { self.delegates = [NSHashTable weakObjectsHashTable]; for (id delegate in delegates) { if (![delegates isKindOfClass:[NSObject class]]) { continue; } [self.delegates addObject:delegate]; } } return self; } //...etc @end
  11. DelegateTransporter.m - (BOOL)respondsToSelector:(SEL)aSelector { for (NSObject *delegate in self.delegates) {

    if ([delegate isKindOfClass:[NSNull class]]) { continue; } if ([delegate respondsToSelector:aSelector]) { return YES; } } return NO; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { for (NSObject *delegate in self.delegates) { if ([delegate isKindOfClass:[NSNull class]]) { continue; } if ([delegate respondsToSelector:aSelector]) { return [delegate methodSignatureForSelector:aSelector]; } } return nil; } - (void)forwardInvocation:(NSInvocation *)anInvocation { for (NSObject *delegate in self.delegates) { if ([delegate isKindOfClass:[NSNull class]]) { continue; } if ([delegate respondsToSelector:anInvocation.selector]) { [anInvocation invokeWithTarget:delegate]; } } }
  12. UITableView.ReachExtension extension UITableView { final class ReachExtension: NSObject { private

    var delegateTransporter: UITableViewDelegateTransporter? { didSet { base?.delegate = delegateTransporter } } weak var delegate: UITableViewDelegate? { didSet { guard let delegate = delegate else { delegateTransporter = nil return } delegateTransporter = UITableViewDelegateTransporter(delegates: [delegate, self]) } } fileprivate var reachedBottom: Bool = false { didSet { if reachedBottom == oldValue { return } guard let base = base, reachedBottom else { return } scrollViewDidReachBottom?(base) } } var scrollViewDidReachBottom: ((UIScrollView) -> ())? private weak var base: UITableView? init(_ base: UITableView) { self.base = base super.init() } } }
  13. UITableView.ReachExtension extension UITableView.ReachExtension: UITableViewDelegate { func scrollViewDidScroll(_ scrollView: UIScrollView) {

    let maxDistance = max(0, scrollView.contentSize.height - scrollView.bounds.size.height) reachedBottom = maxDistance < scrollView.contentOffset.y } }
  14. UITableView extension UITableView { private struct AssociatedKey { static var

    re: UInt8 = 0 } var re: ReachExtension { guard let re = objc_getAssociatedObject(self, &AssociatedKey.re) as? ReachExtension else { let re = ReachExtension(self) objc_setAssociatedObject(self, &AssociatedKey.re, re, .OBJC_ASSOCIATION_RETAIN) return re } return re } }
  15. UITableView.ReverseExtension https://github.com/marty-suzuki/ReverseExtension extension UITableView { final class ReverseExtension: NSObject {

    //some implementation //... private var lastScrollIndicatorInsets: UIEdgeInsets? private var lastContentInset: UIEdgeInsets? private var mutex = pthread_mutex_t() init(_ base: UITableView) { self.base = base super.init() configureTableView(base) } //some implementation //... } }
  16. UITableView.ReverseExtension private func configureTableView(_ tableView: UITableView) { guard let base

    = self.base else { return } if base.transform == CGAffineTransform.identity { UIView.setAnimationsEnabled(false) base.transform = CGAffineTransform.identity.rotated(by: .pi) UIView.setAnimationsEnabled(true) } tableView.addObserver(self, forKeyPath: #keyPath(UITableView.contentInset), options: [.new, .old], context: nil) } public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { switch keyPath { case (#keyPath(UITableView.contentInset))?: DispatchQueue.global().async { [weak self] in DispatchQueue.main.async { self?.configureTableViewInsets() } } default: break } }
  17. UITableView.ReverseExtension private func configureTableViewInsets() { defer { pthread_mutex_unlock(&mutex) } pthread_mutex_lock(&mutex)

    guard let base = base else { return } if let _ = self.lastContentInset, let _ = self.lastScrollIndicatorInsets { return } let contentInset = base.contentInset base.contentInset.bottom = contentInset.top base.contentInset.top = contentInset.bottom self.lastContentInset = base.contentInset let scrollIndicatorInsets = base.scrollIndicatorInsets base.scrollIndicatorInsets.bottom = scrollIndicatorInsets.top base.scrollIndicatorInsets.top = scrollIndicatorInsets.bottom base.scrollIndicatorInsets.right = base.bounds.size.width - 8 self.lastScrollIndicatorInsets = base.scrollIndicatorInsets }
  18. UITableView.ReverseExtension extension UITableView.ReverseExtension: UITableViewDelegate { func tableView(_ tableView: UITableView, willDisplay

    cell: UITableViewCell, forRowAt indexPath: IndexPath) { if cell.transform == CGAffineTransform.identity { UIView.setAnimationsEnabled(false) cell.transform = CGAffineTransform.identity.rotated(by: .pi) UIView.setAnimationsEnabled(true) } } }