summaryrefslogtreecommitdiffhomepage
path: root/ios/Routing/PresentationControllerDismissalInterceptor.swift
blob: da02c2f886680f030f17852d0057f5935e33743a (plain)
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
//
//  PresentationControllerDismissalInterceptor.swift
//  MullvadVPN
//
//  Created by pronebird on 20/02/2023.
//  Copyright © 2025 Mullvad VPN AB. All rights reserved.
//

import UIKit

/**
 Presentation controller delegate class that intercepts interactive dismissal and calls
 `dismissHandler` closure. Forwards all delegate calls to the `forwardingTarget`.
 */
final class PresentationControllerDismissalInterceptor: NSObject,
    UIAdaptivePresentationControllerDelegate
{
    private let dismissHandler: (UIPresentationController) -> Void
    nonisolated(unsafe) private let forwardingTarget: UIAdaptivePresentationControllerDelegate?
    private let protocolSelectors: [Selector]

    init(
        forwardingTarget: UIAdaptivePresentationControllerDelegate?,
        dismissHandler: @escaping (UIPresentationController) -> Void
    ) {
        self.forwardingTarget = forwardingTarget
        self.dismissHandler = dismissHandler

        protocolSelectors = getProtocolMethods(
            UIAdaptivePresentationControllerDelegate.self,
            isRequired: false,
            isInstanceMethod: true
        )
    }

    override func responds(to aSelector: Selector!) -> Bool {
        super.responds(to: aSelector)
            || (protocolSelectors.contains(aSelector) && forwardingTarget?.responds(to: aSelector) ?? false)
    }

    override func forwardingTarget(for aSelector: Selector!) -> Any? {
        if protocolSelectors.contains(aSelector) {
            if super.responds(to: aSelector) {
                return nil
            } else if forwardingTarget?.responds(to: aSelector) ?? false {
                return forwardingTarget
            }
        }
        return super.forwardingTarget(for: aSelector)
    }

    func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
        dismissHandler(presentationController)
        forwardingTarget?.presentationControllerDidDismiss?(presentationController)
    }
}

private func getProtocolMethods(
    _ protocolType: Protocol,
    isRequired: Bool,
    isInstanceMethod: Bool
) -> [Selector] {
    var methodCount: UInt32 = 0
    let methodDescriptions = protocol_copyMethodDescriptionList(
        protocolType,
        isRequired,
        isInstanceMethod,
        &methodCount
    )

    defer { methodDescriptions.map { free($0) } }

    return (0..<methodCount).compactMap { index in
        methodDescriptions?[Int(index)].name
    }
}