Skip to content

Commit 80cd7b5

Browse files
authored
Add ContextMenu support for FluidStack (#174)
1 parent e8d1dcc commit 80cd7b5

File tree

3 files changed

+125
-63
lines changed

3 files changed

+125
-63
lines changed

Development/FluidInterfaceKit.xcodeproj/xcshareddata/xcdebugger/Breakpoints_v2.xcbkptlist

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,25 +22,14 @@
2222
</BreakpointActionProxy>
2323
</Actions>
2424
<Locations>
25-
<Location
26-
uuid = "A2EF7869-1A53-4958-B6C1-1F0B223F1FBA - ca8a62ba89de0751"
27-
shouldBeEnabled = "Yes"
28-
ignoreCount = "0"
29-
continueAfterRunningActions = "No"
30-
symbolName = "UIKit.UIApplicationMain(Swift.Int32, Swift.Optional&lt;Swift.UnsafeMutablePointer&lt;Swift.UnsafeMutablePointer&lt;Swift.Int8&gt;&gt;&gt;, Swift.Optional&lt;Swift.String&gt;, Swift.Optional&lt;Swift.String&gt;) -&gt; Swift.Int32"
31-
moduleName = "libswiftUIKit.dylib"
32-
usesParentBreakpointCondition = "Yes"
33-
offsetFromSymbolStart = "0">
34-
</Location>
3525
<Location
3626
uuid = "A2EF7869-1A53-4958-B6C1-1F0B223F1FBA - dc2f8c5e001177f0"
3727
shouldBeEnabled = "Yes"
3828
ignoreCount = "0"
3929
continueAfterRunningActions = "No"
4030
symbolName = "UIApplicationMain"
4131
moduleName = "UIKitCore"
42-
usesParentBreakpointCondition = "Yes"
43-
offsetFromSymbolStart = "0">
32+
usesParentBreakpointCondition = "Yes">
4433
</Location>
4534
<Location
4635
uuid = "A2EF7869-1A53-4958-B6C1-1F0B223F1FBA - ed0225d50b1c0976"
@@ -49,8 +38,7 @@
4938
continueAfterRunningActions = "No"
5039
symbolName = "UIKit.UIApplicationMain(Swift.Int32, Swift.Optional&lt;Swift.UnsafeMutablePointer&lt;Swift.UnsafeMutablePointer&lt;Swift.Int8&gt;&gt;&gt;, Swift.Optional&lt;Swift.String&gt;, Swift.Optional&lt;Swift.String&gt;) -&gt; Swift.Int32"
5140
moduleName = "UIKitCore"
52-
usesParentBreakpointCondition = "Yes"
53-
offsetFromSymbolStart = "0">
41+
usesParentBreakpointCondition = "Yes">
5442
</Location>
5543
</Locations>
5644
</BreakpointContent>

Development/Sources/FluidInterfaceKit-Demo/DemoContextMenuViewController.swift

Lines changed: 16 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ import StorybookKit
66
import UIKit
77

88
@available(iOS 15, *)
9-
final class DemoContextMenuViewController: UIViewController {
9+
final class DemoContextMenuViewController: FluidStackController {
10+
11+
init() {
12+
super.init(configuration: .init(retainsRootViewController: false))
13+
}
1014

1115
override func viewDidLoad() {
1216
super.viewDidLoad()
@@ -21,7 +25,16 @@ final class DemoContextMenuViewController: UIViewController {
2125
$0.backgroundColor = .neon(.yellow)
2226
}
2327

24-
let interaction = UIContextMenuInteraction(delegate: self)
28+
let interaction = StandaloneContextMenuInteraction.init(
29+
targetStackController: self,
30+
destinationViewController: {
31+
DemoListDetailViewController(
32+
viewModel: .init(),
33+
removingTransitionProvider: { .vanishing }
34+
)
35+
.fluidWrapped(configuration: .init(transition: .empty, topBar: .hidden))
36+
}
37+
)
2538

2639
let view = AnyUIView { _ in
2740
ZStackBlock {
@@ -45,57 +58,11 @@ final class DemoContextMenuViewController: UIViewController {
4558

4659
gridView.setContents(cells)
4760

48-
Mondrian.buildSubviews(on: view) {
61+
Mondrian.buildSubviews(on: contentView) {
4962
ZStackBlock {
5063
gridView.viewBlock.alignSelf(.attach(.all))
5164
}
5265
}
5366

5467
}
5568
}
56-
57-
@available(iOS 15, *)
58-
extension DemoContextMenuViewController: UIContextMenuInteractionDelegate {
59-
func contextMenuInteraction(
60-
_ interaction: UIContextMenuInteraction,
61-
configurationForMenuAtLocation location: CGPoint
62-
) -> UIContextMenuConfiguration? {
63-
64-
return UIContextMenuConfiguration(
65-
identifier: nil,
66-
previewProvider: {
67-
DemoListDetailViewController(viewModel: .init(), removingTransitionProvider: { fatalError() })
68-
},
69-
actionProvider: {
70-
suggestedActions in
71-
let inspectAction =
72-
UIAction(
73-
title: NSLocalizedString("InspectTitle", comment: ""),
74-
image: UIImage(systemName: "arrow.up.square")
75-
) { action in
76-
77-
}
78-
79-
let duplicateAction =
80-
UIAction(
81-
title: NSLocalizedString("DuplicateTitle", comment: ""),
82-
image: UIImage(systemName: "plus.square.on.square")
83-
) { action in
84-
85-
}
86-
87-
let deleteAction =
88-
UIAction(
89-
title: NSLocalizedString("DeleteTitle", comment: ""),
90-
image: UIImage(systemName: "trash"),
91-
attributes: .destructive
92-
) { action in
93-
94-
}
95-
96-
return UIMenu(title: "", children: [inspectAction, duplicateAction, deleteAction])
97-
}
98-
)
99-
}
100-
101-
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import UIKit
2+
3+
/**
4+
It runs standalone, without using delegate.
5+
Passing any actions distinguished each interaction
6+
*/
7+
public final class StandaloneContextMenuInteraction: UIContextMenuInteraction {
8+
9+
private let proxy: _InteractionDelegateProxy
10+
11+
public init(
12+
makeConfiguration: @escaping (CGPoint) -> UIContextMenuConfiguration,
13+
willPerformPreviewAction: @escaping @MainActor (
14+
UIContextMenuConfiguration, any UIContextMenuInteractionCommitAnimating
15+
) -> Void
16+
) {
17+
18+
let proxy = _InteractionDelegateProxy(
19+
makeConfiguration: makeConfiguration,
20+
willPerformPreviewAction: willPerformPreviewAction
21+
)
22+
self.proxy = proxy
23+
super.init(delegate: proxy)
24+
}
25+
26+
/**
27+
Convenience inititalizer for FluidStackController
28+
*/
29+
public init(
30+
targetStackController: FluidStackController,
31+
destinationViewController: @escaping @MainActor () -> FluidViewController
32+
) {
33+
34+
let proxy = _InteractionDelegateProxy(
35+
makeConfiguration: {
36+
location in
37+
UIContextMenuConfiguration(
38+
identifier: nil,
39+
previewProvider: {
40+
let destination = destinationViewController()
41+
return destination
42+
},
43+
actionProvider: { _ in
44+
return UIMenu(title: "", children: [])
45+
}
46+
)
47+
},
48+
willPerformPreviewAction: {
49+
[weak targetStackController] configuration,
50+
animator in
51+
52+
guard let targetStackController else {
53+
return
54+
}
55+
56+
animator.addCompletion {
57+
targetStackController.fluidPush(
58+
animator.previewViewController as! FluidViewController,
59+
target: .current,
60+
relation: .modality,
61+
transition: nil,
62+
completion: nil
63+
)
64+
}
65+
66+
}
67+
)
68+
self.proxy = proxy
69+
super.init(delegate: proxy)
70+
71+
}
72+
73+
}
74+
75+
private final class _InteractionDelegateProxy: NSObject, UIContextMenuInteractionDelegate {
76+
77+
private let makeConfiguration: @MainActor (CGPoint) -> UIContextMenuConfiguration
78+
private let willPerformPreviewAction:
79+
@MainActor (UIContextMenuConfiguration, any UIContextMenuInteractionCommitAnimating) -> Void
80+
81+
init(
82+
makeConfiguration: @escaping @MainActor (CGPoint) -> UIContextMenuConfiguration,
83+
willPerformPreviewAction: @escaping @MainActor (
84+
UIContextMenuConfiguration, any UIContextMenuInteractionCommitAnimating
85+
) -> Void
86+
) {
87+
self.makeConfiguration = makeConfiguration
88+
self.willPerformPreviewAction = willPerformPreviewAction
89+
}
90+
91+
func contextMenuInteraction(
92+
_ interaction: UIContextMenuInteraction,
93+
configurationForMenuAtLocation location: CGPoint
94+
) -> UIContextMenuConfiguration? {
95+
96+
makeConfiguration(location)
97+
}
98+
99+
func contextMenuInteraction(
100+
_ interaction: UIContextMenuInteraction,
101+
willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration,
102+
animator: any UIContextMenuInteractionCommitAnimating
103+
) {
104+
willPerformPreviewAction(configuration, animator)
105+
}
106+
107+
}

0 commit comments

Comments
 (0)