Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EditPage 자잘한 버그수정 (높이 이슈, 페이지 추가시 스크롤) #146

Merged
merged 9 commits into from
Dec 4, 2024
2 changes: 1 addition & 1 deletion MemorialHouse/MHDomain/MHDomain/Entity/MediaType.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
public enum MediaType: String, Sendable {
public enum MediaType: String, CaseIterable, Sendable {
case image
case video
case audio
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import MHFoundation
import MHDomain
import MHCore
import UIKit
import Combine
Expand Down Expand Up @@ -229,9 +230,11 @@ final class EditBookViewController: UIViewController {
.receive(on: DispatchQueue.main)
.sink { [weak self] event in
switch event {
case .updateViewController(let title):
case let .updateViewController(title):
self?.navigationItem.title = title
self?.editPageTableView.reloadData()
case let .pageAdded(at):
self?.insertPage(at: at)
case .saveDone:
guard let self else { return }
switch self.mode {
Expand All @@ -242,6 +245,8 @@ final class EditBookViewController: UIViewController {
}
case .revokeDone:
self?.navigationController?.popViewController(animated: true)
case let .addableMediaTypes(mediaTypes):
self?.updateAddMediaButtonStates(by: mediaTypes)
case .error(let message):
self?.showErrorAlert(with: message)
}
Expand Down Expand Up @@ -309,6 +314,19 @@ final class EditBookViewController: UIViewController {
addPageButton.addAction(addPageAction, for: .touchUpInside)
}

// MARK: - Helper
private func updateAddMediaButtonStates(by mediaTypes: [MediaType]) {
addImageButton.isEnabled = mediaTypes.contains(.image)
addVideoButton.isEnabled = mediaTypes.contains(.video)
addAudioButton.isEnabled = mediaTypes.contains(.audio)
}

private func insertPage(at index: Int) {
let indexPath = IndexPath(row: index, section: 0)
editPageTableView.insertRows(at: [indexPath], with: .automatic)
editPageTableView.scrollToRow(at: indexPath, at: .bottom, animated: true)
}

// MARK: - Keyboard Appear & Hide
@objc
private func keyboardWillAppear(_ notification: Notification) {
Expand Down
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그..코드가..결국 소멸됐군요.. ^^

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

결국... 마참내...

Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ final class EditPageCell: UITableViewCell {
textView.autocorrectionType = .no
textView.autocapitalizationType = .none
textView.spellCheckingType = .no
textView.isScrollEnabled = false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

문제의 '그' 코드,, 고생하셨습니다
근데 혹시 왜 False 했을 때 개선되는지 알게 되셨나요 ??

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

하하 아무리 찾아도 안나와요...ㅠㅠ


return textView
}()
Expand Down Expand Up @@ -282,56 +281,26 @@ extension EditPageCell: @preconcurrency MediaAttachmentDataSource {
extension EditPageCell: UITextViewDelegate {
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
guard let textStorage else { return false }
guard let viewText = textView.text else { return false }
let startIndex = textView.text.startIndex
// Attachment지우기 전에 드래그해서 알려주기
if text.isEmpty && range.length == 1
&& attachmentAt(range.location) != nil
&& textView.selectedRange.length == 0 {
textView.selectedRange = NSRange(location: range.location, length: 1)
return false
}
// 갖은 경우의 수 별로 cachedViewProvider를 nil로 변경합니다.
// 왜냐하면, 줄바꿈을 추가했을 때, 캐시된 View가 업데이트 되어야 하기 때문입니다.
if range.location > 0
&& viewText[viewText.index(startIndex, offsetBy: range.location-1)] == "\n"
&& text.count == 1,
let attachment = (attachmentAt(range.location) ?? attachmentAt(min(range.location+1, viewText.count-1))) {
attachment.cachedViewProvider = nil
}
if text == "\n" { // 줄바꿈을 추가할때
if let attachment = attachmentAt(range.location) { // Attachment 앞에 줄바꿈을 추가할때
attachment.cachedViewProvider = nil
}
else if let attachment = attachmentAt(range.location+1) { // Attachment 1칸 앞에 줄바꿈을 추가할때
attachment.cachedViewProvider = nil
}
}
if text.isEmpty { // 지우기할때
if let attachment = attachmentAt(range.location+1) { // Attachment 1칸 앞에 줄바꿈을 추가할때
if range.location < 1 { // 첫째 줄에 도달하기 전에 지우기할때
attachment.cachedViewProvider = nil
return true
}
textView.selectedRange = NSRange(location: range.location, length: 0)
return false
}
else if let attachment = attachmentAt(range.location+2), // Attachment 2칸 앞에 줄바꿈을 추가할때
textView.text[textView.text.index(textView.text.startIndex, offsetBy: range.location+1)] == "\n" {
attachment.cachedViewProvider = nil
}
Comment on lines -294 to -322
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그럼 이 코드도 textView.isScrollEnabled = false 때문이었나요 ??

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

맞습니다... 수동 최적화의 흔적...

}

let attributedText = NSMutableAttributedString(
string: text,
attributes: defaultAttributes
)
return text.isEmpty
|| isAcceptableHeight(textStorage, shouldChangeTextIn: range, replacementText: attributedText)
return isAcceptableHeight(textStorage, shouldChangeTextIn: range, replacementText: attributedText)
|| text.isEmpty
}

func textViewDidBeginEditing(_ textView: UITextView) {
input.send(.didBeginEditingPage)
let availableHeight = textViewMaxContentSize().height - textView.contentSize.height
input.send(.isMediaAddable(availableHeight: availableHeight))
}

/// TextView의 높이가 적절한지 확인합니다.
Expand All @@ -341,19 +310,31 @@ extension EditPageCell: UITextViewDelegate {
replacementText attributedText: NSAttributedString
) -> Bool {
let updatedText = NSMutableAttributedString(attributedString: textStorage)
let horizontalInset = textView.textContainerInset.left + textView.textContainerInset.right
let verticalInset = textView.textContainerInset.top + textView.textContainerInset.bottom
let textViewWidth = textView.bounds.width - horizontalInset
let textViewHight = textView.bounds.height - verticalInset
let textViewSize = textViewMaxContentSize()
let temporaryTextView = UITextView(
frame: CGRect(x: 0, y: 0, width: textViewWidth, height: .greatestFiniteMagnitude)
frame: CGRect(x: 0, y: 0, width: textViewSize.width, height: .greatestFiniteMagnitude)
)

updatedText.replaceCharacters(in: range, with: attributedText)
temporaryTextView.attributedText = updatedText
temporaryTextView.sizeToFit()

return temporaryTextView.contentSize.height <= textViewHight
let availableHeight = textViewSize.height - temporaryTextView.contentSize.height
let result = availableHeight >= 0

if result {
input.send(.isMediaAddable(availableHeight: availableHeight))
}

return result
}
private func textViewMaxContentSize() -> CGSize {
let horizontalInset = textView.textContainerInset.left + textView.textContainerInset.right
let verticalInset = textView.textContainerInset.top + textView.textContainerInset.bottom
let textViewWidth = textView.bounds.width - horizontalInset
let textViewHeight = textView.bounds.height - verticalInset

return CGSize(width: textViewWidth, height: textViewHeight)
}
}

Expand All @@ -366,7 +347,7 @@ extension EditPageCell: @preconcurrency NSTextStorageDelegate {
changeInLength delta: Int
) {
// 입력하는 곳 앞에 Attachment가 있을 때, 줄바꿈을 추가합니다.
guard delta > 0 else { return }
guard delta >= 0 else { return }
lineBreakForAttachment()
}

Expand All @@ -392,21 +373,21 @@ extension EditPageCell: @preconcurrency NSTextStorageDelegate {
let range = NSRange(location: 0, length: currentString.count)
let newLine = NSAttributedString(string: "\n", attributes: defaultAttributes)
textStorage?.enumerateAttribute(.attachment, in: range, using: { value, range, _ in
guard let attachment = value as? MediaAttachment else { return }
guard value is MediaAttachment else { return }
let location = range.location
// Attachment 앞에 줄바꿈이 없을 때, 줄바꿈을 추가합니다.
if location > 0 {
if currentString[currentString.index(startIndex, offsetBy: location-1)] != "\n" {
textStorage?.insert(newLine, at: location)
attachment.cachedViewProvider = nil
}
}
// Attachment 뒤에 줄바꿈이 없을 때, 줄바꿈을 추가합니다.
let nextLocation = location + range.length
if nextLocation < currentString.count
&& currentString[currentString.index(startIndex, offsetBy: nextLocation)] != "\n" {
textStorage?.insert(newLine, at: nextLocation)
guard nextLocation+1 < textStorage?.length ?? -1 else { return }
guard nextLocation+1 < textStorage?.length ?? -1,
textView.selectedRange.location == nextLocation else { return }
textView.selectedRange = NSRange(location: nextLocation+1, length: 0)
}
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ protocol MediaAttachmentDataSource: AnyObject {
final class MediaAttachment: NSTextAttachment {
// MARK: - Property
private let view: (UIView & MediaAttachable)
var cachedViewProvider: MediaAttachmentViewProvider?
let mediaDescription: MediaDescription
weak var dataSource: MediaAttachmentDataSource?

Expand All @@ -29,9 +28,6 @@ final class MediaAttachment: NSTextAttachment {
location: any NSTextLocation,
textContainer: NSTextContainer?
) -> NSTextAttachmentViewProvider? {
if let provider = cachedViewProvider {
return provider
}
let provider = MediaAttachmentViewProvider(
textAttachment: self,
parentView: parentView,
Expand All @@ -41,7 +37,6 @@ final class MediaAttachment: NSTextAttachment {
provider.tracksTextAttachmentViewBounds = true
provider.view = view
provider.type = mediaDescription.type
cachedViewProvider = provider

return provider
}
Expand All @@ -50,7 +45,6 @@ final class MediaAttachment: NSTextAttachment {
textContainer: NSTextContainer?,
characterIndex charIndex: Int
) -> UIImage? {
cachedViewProvider = nil
return dataSource?.mediaAttachmentDragingImage(self, about: view)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,7 @@ final class MediaAttachmentViewProvider: NSTextAttachmentViewProvider {
// MARK: - Property
var type: MediaType?
private var height: CGFloat {
switch type { // TODO: - 조정 필요
case .image:
300
case .video:
400
case .audio:
100
case nil:
10
default:
100
}
type?.height ?? 100
}

override func attachmentBounds(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ final class EditBookViewModel: ViewModelType {
}
enum Output {
case updateViewController(title: String)
case pageAdded(at: Int)
case saveDone
case revokeDone
case addableMediaTypes([MediaType])
case error(message: String)
}

Expand Down Expand Up @@ -137,6 +139,7 @@ final class EditBookViewModel: ViewModelType {
MHLogger.error(error.localizedDescription + #function)
}
}

private func addEmptyPage() {
let editPageViewModel = EditPageViewModel(
fetchMediaUseCase: fetchMediaUseCase,
Expand All @@ -146,7 +149,7 @@ final class EditBookViewModel: ViewModelType {
)
editPageViewModel.delegate = self
editPageViewModels.append(editPageViewModel)
output.send(.updateViewController(title: title))
output.send(.pageAdded(at: editPageViewModels.count-1))
}

private func saveMediaAll() async {
Expand Down Expand Up @@ -190,4 +193,11 @@ extension EditBookViewModel: EditPageViewModelDelegate {
let pageID = page.id
currentPageIndex = editPageViewModels.firstIndex { $0.page.id == pageID } ?? 0
}

func updateAddableMediaTypes(_ editPageViewModel: EditPageViewModel, mediaTypes: [MediaType]) {
let pageID = editPageViewModel.page.id
let currentPage = editPageViewModels[currentPageIndex]
guard pageID == currentPage.page.id else { return }
output.send(.addableMediaTypes(mediaTypes))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import MHCore

protocol EditPageViewModelDelegate: AnyObject {
func didBeginEditingPage(_ editPageViewModel: EditPageViewModel, page: Page)
func updateAddableMediaTypes(_ editPageViewModel: EditPageViewModel, mediaTypes: [MediaType])
}

final class EditPageViewModel: ViewModelType {
Expand All @@ -16,6 +17,7 @@ final class EditPageViewModel: ViewModelType {
case didEditPage(attributedText: NSAttributedString)
case didRequestMediaDataForData(media: MediaDescription)
case didRequestMediaDataForURL(media: MediaDescription)
case isMediaAddable(availableHeight: Double)
}
enum Output {
case page(page: Page)
Expand Down Expand Up @@ -64,6 +66,8 @@ final class EditPageViewModel: ViewModelType {
Task { await self?.loadMediaForData(media: media) }
case .didRequestMediaDataForURL(let media):
Task { await self?.loadMediaForURL(media: media) }
case let .isMediaAddable(availableHeight):
self?.isMediaAddable(forAvailableHeight: availableHeight)
}
}.store(in: &cancellables)

Expand Down Expand Up @@ -104,6 +108,11 @@ final class EditPageViewModel: ViewModelType {
}
}

private func isMediaAddable(forAvailableHeight height: Double) {
let availableMediaTypes = MediaType.allCases.filter { $0.height <= height }
delegate?.updateAddableMediaTypes(self, mediaTypes: availableMediaTypes)
}

// MARK: - Method
func addMedia(media: MediaDescription, data: Data) {
output.send(.mediaAddedWithData(media: media, data: data))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import MHDomain

extension MediaType {
var height: Double {
Comment on lines +3 to +4
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

옷 혹시 파일 분리하신 이유가 뭐죵 ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Presentaion에서만 사용하는 코드가 Domain에 생기면 약간 다른곳들도 접근가능하게 되는 것같아서 프레젠테이션 모듈 내에서만 쓰도록 extension으로 뺐습니다!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아아 확인했습니다 좋아요 ~!

switch self {
case .image:
return 300
case .video:
return 400
case .audio:
return 100
default:
return 100
}
}
}
Loading