Skip to content

Commit

Permalink
[Merge] #234 - 링크 저장 리팩토링
Browse files Browse the repository at this point in the history
[Refactor] #233 - 링크 저장 리팩토링
  • Loading branch information
mcrkgus authored Nov 18, 2024
2 parents 9983ec9 + 0b94371 commit 72a2ff2
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// Created by 김다예 on 12/30/23.
//

import Combine
import UIKit

import SnapKit
Expand All @@ -30,22 +31,19 @@ final class AddLinkViewController: UIViewController {
private weak var delegate: AddLinkViewControllerPopDelegate?
private weak var urldelegate: SelectClipViewControllerDelegate?

// MARK: - UI Components

private var addLinkView = AddLinkView()
private var viewModel = AddLinkViewModel()
private var cancelBag = CancelBag()

// MARK: - Life Cycle

override func viewDidLoad() {
super.viewDidLoad()

bindViewModels()
setupStyle()
setupAddLinkVew()
hideKeyboard()

setupBinding()
updateUI()
}

override func viewWillAppear(_ animated: Bool) {
Expand All @@ -72,8 +70,13 @@ extension AddLinkViewController {
/// 클립보드 붙여넣기 Alert -> 붙여넣기 허용 클릭 후 자동 링크 임베드를 위한 함수
func embedURL(url: String) {
addLinkView.linkEmbedTextField.becomeFirstResponder()
addLinkView.linkEmbedTextField.text = url // 텍스트필드에 text 채우기
viewModel.inputs.embedLinkText(url) // 관리중 ViewModel에도 String 수정 -> UI 반영
addLinkView.linkEmbedTextField.text = url
viewModel.embedLinkText.send(url)

DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.addLinkView.linkEmbedTextField.sendActions(for: .editingChanged)
}

UIPasteboard.general.url = nil
}
}
Expand Down Expand Up @@ -135,31 +138,46 @@ private extension AddLinkViewController {

}

// ViewModel
extension AddLinkViewController {
private func setupBinding() {
addLinkView.linkEmbedTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
}

@objc private func textFieldDidChange(_ textField: UITextField) {
viewModel.inputs.embedLinkText(textField.text ?? "")
updateUI()
}

private func updateUI() {
addLinkView.clearButton.isHidden = viewModel.outputs.isClearButtonHidden
addLinkView.nextTopButton.isEnabled = viewModel.outputs.isNextButtonEnabled
addLinkView.nextTopButton.backgroundColor = viewModel.outputs.nextButtonBackgroundColor
addLinkView.nextBottomButton.isEnabled = viewModel.outputs.isNextButtonEnabled
addLinkView.nextBottomButton.backgroundColor = viewModel.outputs.nextButtonBackgroundColor
addLinkView.linkEmbedTextField.layer.borderColor = viewModel.outputs.textFieldBorderColor.cgColor
addLinkView.linkEmbedTextField.layer.borderWidth = 1
private func bindViewModels() {
let embedLinkText = addLinkView.linkEmbedTextField
.publisher(for: .editingChanged)
.compactMap { [weak self] _ in self?.addLinkView.linkEmbedTextField.text ?? "" }
.eraseToAnyPublisher()

if let errorMessage = viewModel.outputs.linkEffectivenessMessage {
addLinkView.isValidLinkError(errorMessage)
} else {
addLinkView.resetError()
}
let clearButtonTapped = addLinkView.clearButton.publisher(for: .touchUpInside)
.mapVoid()

let input = AddLinkViewModel.Input(embedLinkText: embedLinkText, clearButtonTapped: clearButtonTapped)
let output = viewModel.transform(input, cancelBag: cancelBag)

output.isClearButtonHidden
.sink { [weak self] isHidden in
self?.addLinkView.clearButton.isHidden = isHidden
}
.store(in: cancelBag)

output.isNextButtonEnabled
.sink { [weak self] isEnabled in
self?.addLinkView.nextTopButton.isEnabled = isEnabled
self?.addLinkView.nextTopButton.backgroundColor = isEnabled ? .black850 : .gray200
self?.addLinkView.nextBottomButton.isEnabled = isEnabled
self?.addLinkView.nextBottomButton.backgroundColor = isEnabled ? .black850 : .gray200
}
.store(in: cancelBag)

output.linkEffectivenessMessage
.sink { [weak self] message in
if let errorMessage = message {
self?.addLinkView.isValidLinkError(errorMessage)
self?.addLinkView.linkEmbedTextField.layer.borderColor = UIColor.toasterError.cgColor
self?.addLinkView.linkEmbedTextField.layer.borderWidth = 1
} else {
self?.addLinkView.resetError()
self?.addLinkView.linkEmbedTextField.layer.borderColor = UIColor.clear.cgColor
}
}
.store(in: cancelBag)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,67 +5,66 @@
// Created by Gahyun Kim on 9/19/24.
//

import Combine
import UIKit

protocol AddLinkViewModelInputs {
func embedLinkText(_ text: String)
}

protocol AddLinkViewModelOutputs {
var isClearButtonHidden: Bool { get }
var isNextButtonEnabled: Bool { get }
var nextButtonBackgroundColor: UIColor { get }
var textFieldBorderColor: UIColor { get }
var linkEffectivenessMessage: String? { get }
}

protocol AddLinkViewModelType {
var inputs: AddLinkViewModelInputs { get }
var outputs: AddLinkViewModelOutputs { get }
}

final class AddLinkViewModel: AddLinkViewModelType, AddLinkViewModelInputs, AddLinkViewModelOutputs {
final class AddLinkViewModel: ViewModelType {

// Input
private var embedLink: String = "" {
didSet {
updateOutputs()
}
}
private var cancelBag: CancelBag = CancelBag()

// Output
var isClearButtonHidden: Bool
var isNextButtonEnabled: Bool
var nextButtonBackgroundColor: UIColor
var textFieldBorderColor: UIColor
var linkEffectivenessMessage: String?
let embedLinkText = PassthroughSubject<String, Never>()

init() {
self.isClearButtonHidden = true
self.isNextButtonEnabled = false
self.nextButtonBackgroundColor = .gray200
self.textFieldBorderColor = .clear
self.linkEffectivenessMessage = nil
struct Input {
let embedLinkText: AnyPublisher<String, Never>
let clearButtonTapped: AnyPublisher<Void, Never>
}

func embedLinkText(_ text: String) {
embedLink = text
struct Output {
let isClearButtonHidden = PassthroughSubject<Bool, Never>()
let isNextButtonEnabled = CurrentValueSubject<Bool, Never>(false)
let textFieldBorderColor = PassthroughSubject<UIColor, Never>()
let linkEffectivenessMessage = PassthroughSubject<String?, Never>()
}

var inputs: AddLinkViewModelInputs { return self }
var outputs: AddLinkViewModelOutputs { return self }
func transform(_ input: Input, cancelBag: CancelBag) -> Output {
let output = Output()

let inputText = input.embedLinkText
.merge(with: input.clearButtonTapped.map { "" })
.eraseToAnyPublisher()

inputText
.map { $0.isEmpty }
.sink { isHidden in
output.isClearButtonHidden.send(isHidden)
}
.store(in: cancelBag)

let isValid = inputText
.map { self.isValidURL($0) }
.share()
.eraseToAnyPublisher()

isValid
.combineLatest(inputText.map { !$0.isEmpty })
.map { $0 && $1 }
.sink { isEnabled in
output.isNextButtonEnabled.send(isEnabled)
}
.store(in: cancelBag)

input.embedLinkText
.map { $0.isEmpty ? "링크를 입력해주세요" : (self.isValidURL($0) ? nil : "유효하지 않은 형식의 링크입니다. " ) }
.sink { message in
output.linkEffectivenessMessage.send(message)
}
.store(in: cancelBag)

return output
}
}

private extension AddLinkViewModel {
func updateOutputs() {
let isValid = isValidURL(embedLink)
isClearButtonHidden = embedLink.isEmpty
isNextButtonEnabled = !embedLink.isEmpty && isValid
nextButtonBackgroundColor = isNextButtonEnabled ? .black850 : .gray200
textFieldBorderColor = isValid ? .clear : UIColor.toasterError
linkEffectivenessMessage = isValid ? nil : (embedLink.isEmpty ? "링크를 입력해주세요" : "유효하지 않은 형식의 링크입니다.")
}

func isValidURL(_ urlString: String) -> Bool {
if (urlString.prefix(8) == "https://") || (urlString.prefix(7) == "http://") {
return true
Expand Down

0 comments on commit 72a2ff2

Please sign in to comment.