| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199 |
- //
- // LNNestedScrollView.swift
- // Gami
- //
- // Created by OneeChan on 2026/3/12.
- //
- import Foundation
- import UIKit
- private class ScrollViewWeakWrapper {
- weak var delegate: LNNestedScrollViewDelegate?
- weak var childListView: UIScrollView?
- }
- private var scrollViewWeakWrapperKey: UInt8 = 0
- private extension UIScrollView {
- var minTopOffset: CGFloat {
- contentSize.height - bounds.height - contentInset.top + contentInset.bottom
- }
- var weakWrapper: ScrollViewWeakWrapper {
- get {
- var wrapper = objc_getAssociatedObject(self, &scrollViewWeakWrapperKey)
- as? ScrollViewWeakWrapper
- if wrapper == nil {
- wrapper = ScrollViewWeakWrapper()
- objc_setAssociatedObject(
- self,
- &scrollViewWeakWrapperKey,
- wrapper,
- .OBJC_ASSOCIATION_RETAIN_NONATOMIC
- )
- }
- return wrapper!
- }
- set {
- objc_setAssociatedObject(
- self,
- &scrollViewWeakWrapperKey,
- newValue,
- .OBJC_ASSOCIATION_RETAIN_NONATOMIC
- )
- }
- }
-
- weak var curChildListView: UIScrollView? {
- get { weakWrapper.childListView }
- set { weakWrapper.childListView = newValue }
- }
- }
- extension UIScrollView: LNNestedScrollViewDelegate {
- func listViewDidScroll(_ scrollView: UIScrollView) -> Bool {
- curChildListView = scrollView
-
- if contentOffset.y < minTopOffset - 0.001 {
- return true
- }
-
- contentOffset.y = minTopOffset
- return false
- }
- }
- extension UIScrollView {
- weak var observerDelegate: LNNestedScrollViewDelegate? {
- get { weakWrapper.delegate }
- set { weakWrapper.delegate = newValue }
- }
- }
- protocol LNNestedScrollViewDelegate: NSObject {
- func listViewDidScroll(_ scrollView: UIScrollView) -> Bool
- }
- private class ScrollViewDelegateProxy: NSObject, UIScrollViewDelegate {
- weak var originalDelegate: UIScrollViewDelegate?
- weak var observerDelegate: UIScrollViewDelegate?
-
- func scrollViewDidScroll(_ scrollView: UIScrollView) {
- originalDelegate?.scrollViewDidScroll?(scrollView)
- observerDelegate?.scrollViewDidScroll?(scrollView)
- }
-
- override func forwardingTarget(for aSelector: Selector!) -> Any? {
- if originalDelegate?.responds(to: aSelector) == true {
- return originalDelegate
- }
- return super.forwardingTarget(for: aSelector)
- }
-
- override func responds(to aSelector: Selector!) -> Bool {
- return super.responds(to: aSelector) || originalDelegate?.responds(to: aSelector) == true
- }
- }
- class LNNestedScrollView: UIScrollView, UIScrollViewDelegate, UIGestureRecognizerDelegate {
- weak var scrollDelegate: UIScrollViewDelegate? {
- didSet {
- proxy.originalDelegate = scrollDelegate
- delegate = nil
- delegate = proxy
- }
- }
- private var proxy = ScrollViewDelegateProxy()
-
- override init(frame: CGRect) {
- super.init(frame: frame)
-
- proxy.originalDelegate = scrollDelegate
- proxy.observerDelegate = self
- delegate = proxy
- }
-
- required init?(coder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- func scrollViewDidScroll(_ scrollView: UIScrollView) {
- if observerDelegate?.listViewDidScroll(self) == true {
- scrollView.contentOffset.y = -contentInset.top
- }
-
- if let curChildListView,
- curChildListView.contentOffset.y > -curChildListView.contentInset.top {
- scrollView.contentOffset.y = minTopOffset
- }
- }
-
- public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
- gestureRecognizer.isKind(of: UIPanGestureRecognizer.self) && otherGestureRecognizer.isKind(of: UIPanGestureRecognizer.self)
- }
- }
- private class TableViewDelegateProxy: NSObject, UITableViewDelegate {
- weak var originalDelegate: UITableViewDelegate?
- weak var observerDelegate: UITableViewDelegate?
-
- func scrollViewDidScroll(_ scrollView: UIScrollView) {
- originalDelegate?.scrollViewDidScroll?(scrollView)
- observerDelegate?.scrollViewDidScroll?(scrollView)
- }
-
- override func forwardingTarget(for aSelector: Selector!) -> Any? {
- if originalDelegate?.responds(to: aSelector) == true {
- return originalDelegate
- }
- return super.forwardingTarget(for: aSelector)
- }
-
- override func responds(to aSelector: Selector!) -> Bool {
- super.responds(to: aSelector)
- || originalDelegate?.responds(to: aSelector) == true
- }
- }
- class LNNestedTableView: UITableView, UITableViewDelegate, UIGestureRecognizerDelegate {
- weak var tableDelegate: UITableViewDelegate? {
- didSet {
- proxy.originalDelegate = tableDelegate
- delegate = nil
- delegate = proxy
- }
- }
- private var proxy = TableViewDelegateProxy()
-
- override init(frame: CGRect, style: UITableView.Style) {
- super.init(frame: frame, style: style)
-
- proxy.originalDelegate = tableDelegate
- proxy.observerDelegate = self
- delegate = proxy
- }
-
- required init?(coder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- func scrollViewDidScroll(_ scrollView: UIScrollView) {
- if observerDelegate?.listViewDidScroll(self) == true {
- scrollView.contentOffset.y = -contentInset.top
- }
-
-
- if let curChildListView,
- curChildListView.contentOffset.y > -curChildListView.contentInset.top {
- scrollView.contentOffset.y = minTopOffset
- }
- }
-
- public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
- gestureRecognizer.isKind(of: UIPanGestureRecognizer.self) && otherGestureRecognizer.isKind(of: UIPanGestureRecognizer.self)
- }
- }
|