-
Notifications
You must be signed in to change notification settings - Fork 5
/
EmbeddedScrollViewExtension.swift
127 lines (114 loc) · 4.94 KB
/
EmbeddedScrollViewExtension.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
//
// EmbeddedScrollViewExtension.swift
// EmbeddedScrollView
//
// Created by Yanni Wang on 17/3/21.
//
import UIKit
#if SWIFT_PACKAGE
import SwiftHook
#else
import EasySwiftHook
#endif
private class EmbeddedScrollViewDelegate: NSObject, UIScrollViewDelegate {
weak var originalDelegate: UIScrollViewDelegate?
override func responds(to aSelector: Selector!) -> Bool {
guard !super.responds(to: aSelector) else {
return true
}
guard let originalDelegate = self.originalDelegate else {
return false
}
return originalDelegate.responds(to: aSelector)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
scrollView.didScroll()
if let originalDelegate = self.originalDelegate {
originalDelegate.scrollViewDidScroll?(scrollView)
}
}
override func forwardingTarget(for aSelector: Selector!) -> Any? {
if let target = super.forwardingTarget(for: aSelector) {
return target
}
return self.originalDelegate
}
}
public extension UIScrollView {
private static var embeddedScrollViewKey = 0
@objc var embeddedScrollView: UIScrollView? {
get {
return objc_getAssociatedObject(self, &UIScrollView.embeddedScrollViewKey) as? UIScrollView
}
set {
newValue?.isScrollEnabled = false
objc_setAssociatedObject(self, &UIScrollView.embeddedScrollViewKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
self.setupIfNeed()
}
}
private static var embeddedDelegateKey = 0
private var embeddedDelegate: EmbeddedScrollViewDelegate? {
get {
return objc_getAssociatedObject(self, &UIScrollView.embeddedDelegateKey) as? EmbeddedScrollViewDelegate
}
set {
objc_setAssociatedObject(self, &UIScrollView.embeddedDelegateKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
private func setupIfNeed() {
guard self.embeddedDelegate == nil else {
return
}
let newDelegate = EmbeddedScrollViewDelegate.init()
newDelegate.originalDelegate = self.delegate
self.embeddedDelegate = newDelegate
self.delegate = newDelegate
try! hookInstead(object: self, selector: #selector(setter: UIScrollView.delegate), closure: { _, view, _, delegate in
view.embeddedDelegate?.originalDelegate = delegate
} as @convention(block) ((UIScrollView, Selector, UIScrollViewDelegate?) -> Void, UIScrollView, Selector, UIScrollViewDelegate?) -> Void)
try! hookInstead(object: self, selector: #selector(getter: UIScrollView.delegate), closure: { _, view, _ in
return view.embeddedDelegate?.originalDelegate
} as @convention(block) ((UIScrollView, Selector) -> UIScrollViewDelegate?, UIScrollView, Selector) -> UIScrollViewDelegate?)
}
fileprivate func didScroll() {
guard let embeddedScrollView = self.embeddedScrollView,
let embeddedSuperView = embeddedScrollView.superview,
embeddedScrollView.window != nil else {
return
}
guard self.frame.height.isLessThanOrEqualTo(embeddedScrollView.frame.height) else {
print("The height of the embedded ScrollView must be equal or greater than the height of the outer ScrollView")
return
}
let embeddedFrameY = embeddedSuperView.convert(embeddedScrollView.frame.origin, to: self).y
let diff = self.contentOffset.y - embeddedFrameY
let maxEmbeddedScrollViewOffsetY = embeddedScrollView.contentSize.height - embeddedScrollView.frame.height
let currentEmbeddedScrollViewOffsetY = embeddedScrollView.contentOffset.y
if currentEmbeddedScrollViewOffsetY.isZero {
// ahead
if self.contentOffset.y > embeddedFrameY {
embeddedScrollView.contentOffset.y += diff
self.contentOffset.y = embeddedFrameY
}
} else if currentEmbeddedScrollViewOffsetY.isEqual(to: maxEmbeddedScrollViewOffsetY) {
// tail
if self.contentOffset.y < embeddedFrameY {
embeddedScrollView.contentOffset.y += diff
self.contentOffset.y = embeddedFrameY
}
} else {
// embedded
let newEmbeddedOffsetY = embeddedScrollView.contentOffset.y + diff
if newEmbeddedOffsetY < 0 {
embeddedScrollView.contentOffset.y = 0
self.contentOffset.y = embeddedFrameY + diff
} else if newEmbeddedOffsetY > maxEmbeddedScrollViewOffsetY {
embeddedScrollView.contentOffset.y = maxEmbeddedScrollViewOffsetY
self.contentOffset.y = embeddedFrameY + diff
} else {
embeddedScrollView.contentOffset.y = newEmbeddedOffsetY
self.contentOffset.y = embeddedFrameY
}
}
}
}