Skip to content

Commit 14320e3

Browse files
committed
feat : 선택된 사진들을 좌우 스와이프가 가능한 CollectionView 로 생성 #7
- SelectedPhotoCell 생성 - 스와이프 페이지 인덱싱 적용 - 네비게이션바에 "다음" 버튼 추가
1 parent 8cbc854 commit 14320e3

File tree

2 files changed

+218
-2
lines changed

2 files changed

+218
-2
lines changed

Particle/Particle/Main/HomeTab/AddArticle/SelectSentence/SelectSentenceViewController.swift

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ protocol SelectSentencePresentableListener: AnyObject {
1717

1818
func showEditSentenceModal(with text: String)
1919
func backButtonTapped()
20+
func nextButtonTapped()
2021
}
2122

2223
final class SelectSentenceViewController: UIViewController, SelectSentencePresentable, SelectSentenceViewControllable {
@@ -31,6 +32,9 @@ final class SelectSentenceViewController: UIViewController, SelectSentencePresen
3132
static let height = 44
3233
static let backButtonLeftMargin = 8
3334
static let nextButtonRightMargin = 8
35+
enum CollectionViewCell {
36+
static let width = DeviceSize.width
37+
static let height = DeviceSize.height - Metric.NavigationBar.height - InfoBox.height - 100
3438
}
3539
}
3640

@@ -49,6 +53,14 @@ final class SelectSentenceViewController: UIViewController, SelectSentencePresen
4953
return button
5054
}()
5155

56+
private let nextButton: UIButton = {
57+
let button = UIButton()
58+
button.setTitle("다음", for: .normal)
59+
button.setTitleColor(.systemGray, for: .disabled)
60+
button.setTitleColor(.particleColor.main, for: .normal)
61+
return button
62+
}()
63+
5264
private let navigationTitle: UILabel = {
5365
let label = UILabel()
5466
label.text = "문장 선택 1/7"
@@ -74,6 +86,21 @@ final class SelectSentenceViewController: UIViewController, SelectSentencePresen
7486
let imageView = UIImageView()
7587
imageView.contentMode = .scaleAspectFit
7688
return imageView
89+
private let selectedPhotoCollectionView: UICollectionView = {
90+
let layout = UICollectionViewFlowLayout()
91+
layout.itemSize = CGSize(
92+
width: Metric.CollectionViewCell.width,
93+
height: Metric.CollectionViewCell.height
94+
)
95+
layout.minimumLineSpacing = 0
96+
layout.minimumInteritemSpacing = 0
97+
layout.scrollDirection = .horizontal
98+
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
99+
collectionView.isPagingEnabled = true
100+
collectionView.showsHorizontalScrollIndicator = false
101+
collectionView.register(SelectedPhotoCell.self)
102+
collectionView.backgroundColor = .init(hex: 0x1f1f1f)
103+
return collectionView
77104
}()
78105

79106
private let textView: UITextView = {
@@ -122,6 +149,7 @@ final class SelectSentenceViewController: UIViewController, SelectSentencePresen
122149
setConstraints()
123150
setupNavigationBar()
124151
configureTextView()
152+
bind()
125153
}
126154

127155
private func setupNavigationBar() {
@@ -131,23 +159,68 @@ final class SelectSentenceViewController: UIViewController, SelectSentencePresen
131159
self?.listener?.backButtonTapped()
132160
}
133161
.disposed(by: disposeBag)
162+
163+
nextButton.rx.tap
164+
.bind { [weak self] in
165+
self?.listener?.nextButtonTapped()
166+
}
167+
.disposed(by: disposeBag)
168+
169+
// TODO: 각 사진에서 문장추출이 모두 완료되었을 때 nextButton 활성화
170+
// nextButton.isEnabled = false
134171
}
135172

136173
private func configureTextView() {
137174
textView.delegate = self
175+
private func bind() {
176+
177+
bindCollectionViewCell()
178+
bindPageIndex()
138179
}
139180

140181
private func addCustomMenuItem() {
141182
let menuItem1 = UIMenuItem(title: "문장뽑기", action: #selector(textSelected(_:)))
142183
UIMenuController.shared.menuItems = nil
143184
UIMenuController.shared.menuItems = [menuItem1]
185+
private func bindCollectionViewCell() {
186+
Observable.of(selectedImages)
187+
.bind(to: selectedPhotoCollectionView.rx.items(
188+
cellIdentifier: SelectedPhotoCell.defaultReuseIdentifier,
189+
cellType: SelectedPhotoCell.self)
190+
) { [weak self] index, item, cell in
191+
cell.setImage(with: item)
192+
cell.listener = self
193+
}
194+
.disposed(by: disposeBag)
144195
}
145196

146197
@objc private func textSelected(_ sender: UIMenuController) {
147198
if let selectedRange = textView.selectedTextRange {
148199
let selectedText = textView.text(in: selectedRange) ?? "선택된 문장이 없습니다."
149200
listener?.showEditSentenceModal(with: selectedText)
150201
}
202+
private func bindPageIndex() {
203+
selectedPhotoCollectionView
204+
.rx
205+
.contentOffset
206+
.subscribe { [weak self] point in
207+
guard let self = self, let positionX = point.element?.x else { return }
208+
switch positionX {
209+
case (0..<DeviceSize.width/2):
210+
self.navigationTitle.text = "문장 선택 1/\(self.selectedImages.count)"
211+
case (DeviceSize.width/2..<DeviceSize.width*(3/2)):
212+
self.navigationTitle.text = "문장 선택 2/\(self.selectedImages.count)"
213+
case (DeviceSize.width*(3/2)..<DeviceSize.width*(5/2)):
214+
self.navigationTitle.text = "문장 선택 3/\(self.selectedImages.count)"
215+
case (DeviceSize.width*(5/2)..<DeviceSize.width*(7/2)):
216+
self.navigationTitle.text = "문장 선택 4/\(self.selectedImages.count)"
217+
case (DeviceSize.width*(7/2)..<DeviceSize.width*(9/2)):
218+
self.navigationTitle.text = "문장 선택 5/\(self.selectedImages.count)"
219+
default:
220+
return
221+
}
222+
}
223+
.disposed(by: disposeBag)
151224
}
152225

153226
func recognizeTextImage(_ image: UIImage?) {
@@ -214,6 +287,7 @@ final class SelectSentenceViewController: UIViewController, SelectSentencePresen
214287

215288
// MARK: - UITextViewDelegate
216289
extension SelectSentenceViewController: UITextViewDelegate {
290+
extension SelectSentenceViewController: SelectedPhotoCellListener {
217291

218292
func textViewDidChangeSelection(_ textView: UITextView) {
219293
let selectedRange = textView.selectedRange
@@ -231,11 +305,11 @@ extension SelectSentenceViewController: UITextViewDelegate {
231305
private extension SelectSentenceViewController {
232306

233307
func addSubviews() {
234-
[backButton, navigationTitle].forEach {
308+
[backButton, navigationTitle, nextButton].forEach {
235309
navigationBar.addSubview($0)
236310
}
237311

238-
[navigationBar, infoBox, textView].forEach {
312+
[navigationBar, infoBox, selectedPhotoCollectionView].forEach {
239313
view.addSubview($0)
240314
}
241315

@@ -253,6 +327,11 @@ private extension SelectSentenceViewController {
253327
$0.left.equalToSuperview().inset(Metric.NavigationBar.backButtonLeftMargin)
254328
}
255329

330+
nextButton.snp.makeConstraints {
331+
$0.centerY.equalToSuperview()
332+
$0.right.equalToSuperview().inset(Metric.NavigationBar.nextButtonRightMargin)
333+
}
334+
256335
navigationTitle.snp.makeConstraints {
257336
$0.center.equalToSuperview()
258337
}
@@ -269,6 +348,7 @@ private extension SelectSentenceViewController {
269348
}
270349

271350
textView.snp.makeConstraints {
351+
selectedPhotoCollectionView.snp.makeConstraints {
272352
$0.top.equalTo(infoBox.snp.bottom)
273353
$0.leading.trailing.bottom.equalTo(view.safeAreaLayoutGuide)
274354
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
//
2+
// SelectedPhotoCell.swift
3+
// Particle
4+
//
5+
// Created by 이원빈 on 2023/07/30.
6+
//
7+
8+
import UIKit
9+
import Photos
10+
import VisionKit
11+
12+
protocol SelectedPhotoCellListener: AnyObject {
13+
func copyButtonTapped(with text: String)
14+
}
15+
16+
final class SelectedPhotoCell: UICollectionViewCell {
17+
18+
private let mainScrollView: UIScrollView = {
19+
let scrollView = UIScrollView()
20+
return scrollView
21+
}()
22+
23+
private let imageView: UIImageView = {
24+
let imageView = UIImageView()
25+
imageView.contentMode = .scaleAspectFit
26+
return imageView
27+
}()
28+
29+
private lazy var interaction: ImageAnalysisInteraction = {
30+
let interaction = ImageAnalysisInteraction()
31+
interaction.preferredInteractionTypes = .automatic
32+
interaction.allowLongPressForDataDetectorsInTextMode = true
33+
return interaction
34+
}()
35+
36+
private let imageAnalyzer = ImageAnalyzer()
37+
38+
private var copiedText = ""
39+
weak var listener: SelectedPhotoCellListener?
40+
41+
override init(frame: CGRect) {
42+
super.init(frame: frame)
43+
addSubviews()
44+
setConstraints()
45+
contentView.clipsToBounds = true
46+
NotificationCenter.default.addObserver(
47+
self,
48+
selector: #selector(copyButtonTapped),
49+
name: UIPasteboard.changedNotification,
50+
object: nil
51+
)
52+
}
53+
54+
required init?(coder: NSCoder) {
55+
fatalError("init(coder:) has not been implemented")
56+
}
57+
58+
deinit {
59+
NotificationCenter.default.removeObserver(self)
60+
}
61+
62+
override func prepareForReuse() {
63+
super.prepareForReuse()
64+
imageView.image = nil
65+
}
66+
67+
private func showLiveText() {
68+
guard let image = imageView.image else {
69+
Console.error("imageView.image == nil 입니다.")
70+
return
71+
}
72+
73+
Task {
74+
let configuration = ImageAnalyzer.Configuration([.text])
75+
76+
do {
77+
let analysis = try await imageAnalyzer.analyze(image, configuration: configuration)
78+
79+
DispatchQueue.main.async {
80+
self.interaction.analysis = nil
81+
self.interaction.preferredInteractionTypes = []
82+
83+
self.interaction.analysis = analysis
84+
self.interaction.preferredInteractionTypes = .textSelection
85+
}
86+
} catch {
87+
Console.error(error.localizedDescription)
88+
}
89+
}
90+
}
91+
92+
93+
@objc func copyButtonTapped() {
94+
if let theString = UIPasteboard.general.string {
95+
copiedText = theString
96+
Console.log(copiedText)
97+
listener?.copyButtonTapped(with: copiedText)
98+
}
99+
}
100+
101+
func setImage(with asset: PHAsset) {
102+
imageView.addInteraction(interaction)
103+
104+
imageView.fetchImage(
105+
asset: asset,
106+
contentMode: .default,
107+
targetSize: imageView.frame.size
108+
) { [weak self] aspectRatio in
109+
self?.imageView.snp.makeConstraints {
110+
$0.width.equalTo(DeviceSize.width)
111+
$0.height.equalTo(aspectRatio * DeviceSize.width)
112+
}
113+
self?.showLiveText()
114+
}
115+
}
116+
}
117+
118+
// MARK: - Layout Settting
119+
120+
private extension SelectedPhotoCell {
121+
122+
func addSubviews() {
123+
contentView.addSubview(mainScrollView)
124+
mainScrollView.addSubview(imageView)
125+
}
126+
127+
func setConstraints() {
128+
mainScrollView.snp.makeConstraints {
129+
$0.top.bottom.leading.trailing.equalTo(contentView.safeAreaLayoutGuide)
130+
}
131+
132+
imageView.snp.makeConstraints {
133+
$0.top.bottom.leading.trailing.equalTo(mainScrollView)
134+
}
135+
}
136+
}

0 commit comments

Comments
 (0)