Slide 1

Slide 1 text

Delegateͷڞ௨ॲཧΛ Message ForwardingΛར༻ͯ͠ ೚ҙͷNamespaceͰӅṭ͢Δ AKIBA.swift×SwiftѪ޷ձ vol2 2017/03/06 marty-suzuki

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

NoticeObserveKit https://github.com/marty-suzuki/NoticeObserveKit class ViewController: UIViewController { let pool = NoticeObserverPool() override func viewDidLoad() { super.viewDidLoad() //NoticeType͕࠾༻͞ΕͨstructͷInfoTypeΛ΋ͱʹɺ௨஌͞ΕΔΦϒδΣΫτͷܕਪ࿦Λߦ͍ͬͯΔ UIKeyboardWillShow.observe { [unowned self] in self.keyboardAnimation(with: $0) }.addObserverTo(pool) } func keyboardAnimation(with keyboard: UIKeyboardInfo) { //ΩʔϘʔυͷΞχϝʔγϣϯॲཧ } } struct UIKeyboardWillShow: NoticeType { typealias InfoType = UIKeyboardInfo static let name: Notification.Name = .UIKeyboardWillShow }

Slide 4

Slide 4 text

ͦΕͰ͸ຊ୊ʹ

Slide 5

Slide 5 text

͜ͷΑ͏ͳ࣮૷Λߦ͏ͱ͖ Ͳ͏͠·͔͢ʁ

Slide 6

Slide 6 text

Paging

Slide 7

Slide 7 text

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() { //UITableViewͷఈʹ౸ୡͨ͠ࡍͷॲཧ } func scrollViewDidScroll(_ scrollView: UIScrollView) { let maxDistance = max(0, scrollView.contentSize.height - scrollView.bounds.size.height) reachedBottom = maxDistance < scrollView.contentOffset.y } }

Slide 8

Slide 8 text

Paging Solution

Slide 9

Slide 9 text

͜ͷΑ͏ͳܗͰॻ͚ͨΒϕλʔͰ͸ͳ͍Ͱ͠ΐ͏͔ʁ class ViewController: UIViewController { @IBOutlet weak var tableView: UITableView! override func viewDidLoad() { super.viewDidLoad() //.reΛ௥Ճ͢Δ͚ͩ tableView.re.delegate = self tableView.re.scrollViewDidReachBottom = { scrollView in print("scrollViewDidReachBottom!") } } } extension ViewController: UITableViewDelegate { //ViewController಺ͰDelegateͷϋϯυϦϯά͕Մೳ func scrollViewDidScroll(_ scrollView: UIScrollView) { print("scrollViewDidScroll = \(scrollView.contentOffset.y)") } }

Slide 10

Slide 10 text

Implementation

Slide 11

Slide 11 text

DelegateTransporter.h @interface DelegateTransporter : NSObject - (nonnull instancetype)initWithDelegates:(NSArray * __nonnull)delegates NS_REFINED_FOR_SWIFT; @end

Slide 12

Slide 12 text

DelegateTransporter.h @interface DelegateTransporter : NSObject - (nonnull instancetype)initWithDelegates:(NSArray * __nonnull)delegates NS_REFINED_FOR_SWIFT; @end SwiftͰUITableViewDelegate༻ͷDelegateTransporterΛॻ͘ͷ ͰɺNS_REFINED_FOR_SWIFTʹͯ͠.init(__deleate:)Ͱݺͼ ग़͢Α͏ʹ͢Δɻ

Slide 13

Slide 13 text

DelegateTransporter.m @interface DelegateTransporter () @property (nonnull, nonatomic, strong) NSHashTable *delegates; @end

Slide 14

Slide 14 text

DelegateTransporter.m @interface DelegateTransporter () @property (nonnull, nonatomic, strong) NSHashTable *delegates; @end ഑ྻͰड͚औͬͨdelegateΛऑࢀরͰอ͍࣋ͨ͠ͷͰɺ NSHashTableΛར༻͢Δɻself.delegates͸[NSHashTable weakObjectsHashTable]ͰॳظԽ͢Δɻ

Slide 15

Slide 15 text

DelegateTransporter.m @interface DelegateTransporter () @property (nonnull, nonatomic, strong) NSHashTable *delegates; @end @implementation DelegateTransporter - (instancetype)initWithDelegates:(NSArray *)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

Slide 16

Slide 16 text

Message Forwarding https://developer.apple.com/library/content/documentation/ Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ ocrtForwarding.html Apple Sample Code

Slide 17

Slide 17 text

DelegateTransporter.m - (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]; } } } - (BOOL)respondsToSelector:(SEL)aSelector { for (NSObject *delegate in self.delegates) { if ([delegate isKindOfClass:[NSNull class]]) { continue; } if ([delegate respondsToSelector:aSelector]) { return YES; } } return NO; }

Slide 18

Slide 18 text

UITableViewDelegateTransporter class UITableViewDelegateTransporter: DelegateTransporter, UITableViewDelegate { @nonobjc convenience init(delegates: [UITableViewDelegate]) { self.init(__delegates: delegates) } }

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

reΛফ͚ͩ͢Ͱ௨ৗͷڍಈʹ tableView.re.delegate = self ↓ tableView.delegate = self

Slide 23

Slide 23 text

Advanced Usage

Slide 24

Slide 24 text

No content

Slide 25

Slide 25 text

No content

Slide 26

Slide 26 text

No content

Slide 27

Slide 27 text

No content

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

໰୊఺ • UIViewControllerͳͲͰɺtableView.transform = .init(rotationAngle: .pi)Λهड़ ͠ͳ͚Ε͹ͳΒͳ͍ • UIViewControllerͳͲͰɺUITableViewͷcontentInset΍scrollIndicatorInsetsͳ ͲΛ൓సͤ͞Δهड़Λ͠ͳ͚Ε͹ͳΒͳ͍ • UITableViewCellͷawakeFromNibʹtransform = .init(rotationAngle: .pi)ΛCell ͝ͱʹهड़͠ͳ͚Ε͹ͳΒͳ͍ ൓స͕ෆཁʹͳͬͨ৔߹ʹɺ্هͷ࣮૷Λ͢΂ͯ࡟আ͠ͳ͚Ε ͹ͳΒͳ͘ͳΔ

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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 }

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

tableView(_:willDisplay:forRowAt:) ʹUITableViewCellͷ൓సॲཧΛهड़͢Δ͜ͱͰ ֤UITableViewCellͷawakeFromNibʹ هड़͢Δ͜ͱ͕ෆཁʹ

Slide 35

Slide 35 text

΋͏Ұ౓ݴ͍·͕͢ɺreΛফ͚ͩ͢Ͱ௨ৗͷڍಈʹ tableView.re.delegate = self ↓ tableView.delegate = self

Slide 36

Slide 36 text

ϞόΠϧΞϓϦ։ൃΤΩεύʔτཆ੒ಡຊʢ4/11ൃചʣ https://www.amazon.co.jp/dp/4774188638

Slide 37

Slide 37 text

͝੩ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠ʂ marty-suzuki