Skip to content

Commit ac892b8

Browse files
authored
[#293] 태그를 LongPressGesture로 복붙 가능하도록 구현한다 (#801)
* feat: 태그 복사 메뉴 추가 * fix: 편집 태그 삭제 버튼 터치 영역 복구
1 parent f8aad1b commit ac892b8

3 files changed

Lines changed: 168 additions & 2 deletions

File tree

Application/App/Sources/Resource/Localizable.xcstrings

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,23 @@
290290
}
291291
}
292292
},
293+
"common_copy" : {
294+
"extractionState" : "manual",
295+
"localizations" : {
296+
"en" : {
297+
"stringUnit" : {
298+
"state" : "translated",
299+
"value" : "Copy"
300+
}
301+
},
302+
"ko" : {
303+
"stringUnit" : {
304+
"state" : "translated",
305+
"value" : "복사"
306+
}
307+
}
308+
}
309+
},
293310
"common_delete" : {
294311
"extractionState" : "manual",
295312
"localizations" : {
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
//
2+
// TagCopyMenuView.swift
3+
// PresentationShared
4+
//
5+
// Created by opfic on 8/9/26.
6+
//
7+
8+
import SwiftUI
9+
import UIKit
10+
11+
struct TagCopyMenuView: UIViewRepresentable {
12+
let tagText: String
13+
14+
func makeCoordinator() -> TagCopyMenuCoordinator {
15+
TagCopyMenuCoordinator()
16+
}
17+
18+
func makeUIView(context: Context) -> UIView {
19+
let view = UIView()
20+
view.backgroundColor = .clear
21+
view.isAccessibilityElement = false
22+
context.coordinator.install(on: view)
23+
context.coordinator.tagText = tagText
24+
return view
25+
}
26+
27+
func updateUIView(_ view: UIView, context: Context) {
28+
context.coordinator.tagText = tagText
29+
}
30+
}
31+
32+
final class TagCopyMenuCoordinator: NSObject {
33+
var tagText = ""
34+
private weak var view: UIView?
35+
private var editMenuInteraction: UIEditMenuInteraction?
36+
37+
func install(on view: UIView) {
38+
self.view = view
39+
40+
let editMenuInteraction = UIEditMenuInteraction(delegate: self)
41+
view.addInteraction(editMenuInteraction)
42+
self.editMenuInteraction = editMenuInteraction
43+
44+
let longPressGesture = UILongPressGestureRecognizer(
45+
target: self,
46+
action: #selector(handleLongPressGesture(_:))
47+
)
48+
longPressGesture.allowedTouchTypes = [
49+
NSNumber(value: UITouch.TouchType.direct.rawValue),
50+
NSNumber(value: UITouch.TouchType.indirectPointer.rawValue)
51+
]
52+
longPressGesture.cancelsTouchesInView = false
53+
longPressGesture.delegate = self
54+
view.addGestureRecognizer(longPressGesture)
55+
56+
let secondaryClickGesture = UITapGestureRecognizer(
57+
target: self,
58+
action: #selector(handleSecondaryClickGesture(_:))
59+
)
60+
secondaryClickGesture.allowedTouchTypes = [
61+
NSNumber(value: UITouch.TouchType.indirectPointer.rawValue)
62+
]
63+
secondaryClickGesture.buttonMaskRequired = .secondary
64+
secondaryClickGesture.cancelsTouchesInView = false
65+
secondaryClickGesture.delegate = self
66+
view.addGestureRecognizer(secondaryClickGesture)
67+
}
68+
69+
@objc
70+
private func handleLongPressGesture(_ gesture: UILongPressGestureRecognizer) {
71+
guard gesture.state == .began, let view else { return }
72+
73+
presentEditMenu(at: gesture.location(in: view))
74+
}
75+
76+
@objc
77+
private func handleSecondaryClickGesture(_ gesture: UITapGestureRecognizer) {
78+
guard gesture.state == .ended, let view else { return }
79+
80+
presentEditMenu(at: gesture.location(in: view))
81+
}
82+
83+
private func presentEditMenu(at sourcePoint: CGPoint) {
84+
let configuration = UIEditMenuConfiguration(
85+
identifier: nil,
86+
sourcePoint: sourcePoint
87+
)
88+
editMenuInteraction?.presentEditMenu(with: configuration)
89+
}
90+
}
91+
92+
extension TagCopyMenuCoordinator: UIEditMenuInteractionDelegate {
93+
func editMenuInteraction(
94+
_ interaction: UIEditMenuInteraction,
95+
menuFor configuration: UIEditMenuConfiguration,
96+
suggestedActions: [UIMenuElement]
97+
) -> UIMenu? {
98+
let tagText = tagText
99+
let copyAction = UIAction(title: String(localized: "common_copy")) { _ in
100+
UIPasteboard.general.string = tagText
101+
}
102+
return UIMenu(children: [copyAction])
103+
}
104+
105+
func editMenuInteraction(
106+
_ interaction: UIEditMenuInteraction,
107+
targetRectFor configuration: UIEditMenuConfiguration
108+
) -> CGRect {
109+
view?.bounds ?? .zero
110+
}
111+
}
112+
113+
extension TagCopyMenuCoordinator: UIGestureRecognizerDelegate {
114+
func gestureRecognizer(
115+
_ gestureRecognizer: UIGestureRecognizer,
116+
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer
117+
) -> Bool {
118+
true
119+
}
120+
}

Application/Presentation/PresentationShared/Sources/Common/Component/Tags.swift

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,31 @@ public struct Tag: View {
1212
@State private var height: CGFloat = 0
1313
private let name: String
1414
private let isEditing: Bool
15+
private let isCopyEnabled: Bool
1516
private var action: (() -> Void)?
1617

17-
public init(_ name: String, isEditing: Bool, action: (() -> Void)? = nil) {
18+
public init(
19+
_ name: String,
20+
isEditing: Bool,
21+
action: (() -> Void)? = nil
22+
) {
23+
self.init(
24+
name,
25+
isEditing: isEditing,
26+
isCopyEnabled: false,
27+
action: action
28+
)
29+
}
30+
31+
init(
32+
_ name: String,
33+
isEditing: Bool,
34+
isCopyEnabled: Bool,
35+
action: (() -> Void)?
36+
) {
1837
self.name = name
1938
self.isEditing = isEditing
39+
self.isCopyEnabled = isCopyEnabled
2040
self.action = action
2141
}
2242

@@ -30,6 +50,11 @@ public struct Tag: View {
3050
.padding(.vertical, 4)
3151
.padding(.leading, 8)
3252
.padding(.trailing, isEditing ? 0 : 8)
53+
.overlay {
54+
if isCopyEnabled {
55+
TagCopyMenuView(tagText: name)
56+
}
57+
}
3358
.background {
3459
GeometryReader { geo in
3560
Color.clear
@@ -120,7 +145,11 @@ public struct TagList: View {
120145
@ViewBuilder
121146
private var contentTags: some View {
122147
ForEach(tags, id: \.self) { tagText in
123-
Tag(tagText, isEditing: isEditing) {
148+
Tag(
149+
tagText,
150+
isEditing: isEditing,
151+
isCopyEnabled: true
152+
) {
124153
action?(tagText)
125154
}
126155
}

0 commit comments

Comments
 (0)