Skip to content

Commit

Permalink
[�Merge] #204 - AddLink MVVM 구조로 변경
Browse files Browse the repository at this point in the history
[Refactor] #201 - AddLink MVVM 구조로 변경
  • Loading branch information
mcrkgus authored Sep 28, 2024
2 parents 3580308 + 382500a commit bb0bacf
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 127 deletions.
16 changes: 16 additions & 0 deletions TOASTER-iOS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,8 @@
8315CD8C2B54782F0061F377 /* SelectClipViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8315CD8B2B54782F0061F377 /* SelectClipViewController.swift */; };
8315CD8E2B547EE30061F377 /* SelectClipHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8315CD8D2B547EE30061F377 /* SelectClipHeaderView.swift */; };
8315CD912B5521F70061F377 /* SelectClipModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8315CD902B5521F70061F377 /* SelectClipModel.swift */; };
832F0ED72C9C07EA00E38571 /* AddLinkViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 832F0ED62C9C07EA00E38571 /* AddLinkViewModel.swift */; };
8334CFA02CA6E2D200319922 /* ViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8334CF9F2CA6E2D200319922 /* ViewModelType.swift */; };
83474A6A2BED06EB009B9C48 /* ToasterTargetType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE6DA7B2B54571D008B06FA /* ToasterTargetType.swift */; };
83474A6B2BED06F1009B9C48 /* PatchEditLinkTitleRequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8388E98B2BC8FAB200858C5C /* PatchEditLinkTitleRequestDTO.swift */; };
83474A6C2BED072A009B9C48 /* ToasterAPIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE6DA7D2B54572B008B06FA /* ToasterAPIService.swift */; };
Expand Down Expand Up @@ -502,6 +504,8 @@
8315CD8B2B54782F0061F377 /* SelectClipViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectClipViewController.swift; sourceTree = "<group>"; };
8315CD8D2B547EE30061F377 /* SelectClipHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectClipHeaderView.swift; sourceTree = "<group>"; };
8315CD902B5521F70061F377 /* SelectClipModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectClipModel.swift; sourceTree = "<group>"; };
832F0ED62C9C07EA00E38571 /* AddLinkViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddLinkViewModel.swift; sourceTree = "<group>"; };
8334CF9F2CA6E2D200319922 /* ViewModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModelType.swift; sourceTree = "<group>"; };
8364220B2BE7BFB2005C4085 /* PatchEditLinkTitleResponseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatchEditLinkTitleResponseDTO.swift; sourceTree = "<group>"; };
8388E98B2BC8FAB200858C5C /* PatchEditLinkTitleRequestDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatchEditLinkTitleRequestDTO.swift; sourceTree = "<group>"; };
8388E98D2BC8FC6700858C5C /* EditLinkBottomSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditLinkBottomSheetView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -976,6 +980,7 @@
isa = PBXGroup;
children = (
3F2FA1762B45C3E000EDBF95 /* AuthenticationAdapterProtocol.swift */,
8334CF9F2CA6E2D200319922 /* ViewModelType.swift */,
);
path = Protocols;
sourceTree = "<group>";
Expand Down Expand Up @@ -1579,6 +1584,7 @@
children = (
8309F5862B8DCE8100A1420A /* Model */,
8309F5852B8DCE7B00A1420A /* View */,
832F0ED52C9C07C500E38571 /* ViewModel */,
);
path = LinkEmbed;
sourceTree = "<group>";
Expand Down Expand Up @@ -1635,6 +1641,14 @@
path = ViewModel;
sourceTree = "<group>";
};
832F0ED52C9C07C500E38571 /* ViewModel */ = {
isa = PBXGroup;
children = (
832F0ED62C9C07EA00E38571 /* AddLinkViewModel.swift */,
);
path = ViewModel;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
Expand Down Expand Up @@ -1889,6 +1903,7 @@
8305179D2B4D3701009FFB60 /* MainInfoModel.swift in Sources */,
6BE6DAA42B547579008B06FA /* GetDetailTimerResponseDTO.swift in Sources */,
6BE6D9E22B4E9B58008B06FA /* CompleteTimerCollectionViewCell.swift in Sources */,
8334CFA02CA6E2D200319922 /* ViewModelType.swift in Sources */,
3F2FA1792B45C46F00EDBF95 /* KakaoAuthenticateAdapter.swift in Sources */,
6BE6DA342B50594B008B06FA /* MoyaPlugin.swift in Sources */,
396DCDFA2CA19F2000FEF7C8 /* PopupTargetType.swift in Sources */,
Expand All @@ -1914,6 +1929,7 @@
3F617CB82B4ECB6000956E69 /* MypageUserModel.swift in Sources */,
6BE6DA612B50B742008B06FA /* ClipAPIService.swift in Sources */,
830517AA2B4D95E9009FFB60 /* HomeFooterCollectionView.swift in Sources */,
832F0ED72C9C07EA00E38571 /* AddLinkViewModel.swift in Sources */,
39049C8D2B43EEF400C9196E /* ToastStatus.swift in Sources */,
8305178E2B4D1EF8009FFB60 /* WeeklyRecommendCollectionViewCell.swift in Sources */,
830517902B4D1FC7009FFB60 /* HomeHeaderCollectionView.swift in Sources */,
Expand Down
15 changes: 15 additions & 0 deletions TOASTER-iOS/Global/Protocols/ViewModelType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// ViewModelType.swift
// TOASTER-iOS
//
// Created by Gahyun Kim on 9/27/24.
//

import Foundation

protocol ViewModelType {
associatedtype Input
associatedtype Output

func transform(_ input: Input) -> Output
}
117 changes: 12 additions & 105 deletions TOASTER-iOS/Present/AddLink/LinkEmbed/View/AddLinkView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,15 @@ import Then

final class AddLinkView: UIView {

// MARK: - Properties

private var timer: Timer?
// MARK: - Property

private var keyboardHeight: CGFloat = 100

// MARK: - UI Components

private let descriptLabel = UILabel()
var linkEmbedTextField = UITextField()
private let clearButton = UIButton()
private(set) var linkEmbedTextField = UITextField()
let clearButton = UIButton()

let nextBottomButton = UIButton()
let nextTopButton = UIButton()
Expand All @@ -36,7 +35,7 @@ final class AddLinkView: UIView {
super.init(frame: frame)

setLinkEmbedTextField()
setView()
setupView()
}

@available(*, unavailable)
Expand All @@ -46,14 +45,13 @@ final class AddLinkView: UIView {

// MARK: - Make View

func setView() {
func setupView() {
setupStyle()
setupHierarchy()
setupLayout()
}

func setLinkEmbedTextField() {
linkEmbedTextField.delegate = self
linkEmbedTextField.resignFirstResponder()
}

Expand Down Expand Up @@ -89,6 +87,7 @@ private extension AddLinkView {
clearButton.do {
$0.setImage(.icCancle24, for: .normal)
$0.addTarget(self, action: #selector(cancelButtonTapped), for: .touchUpInside)
$0.isHidden = true
}

nextBottomButton.do {
Expand All @@ -112,7 +111,6 @@ private extension AddLinkView {

func setupHierarchy() {
addSubviews(descriptLabel, linkEmbedTextField, nextBottomButton, clearButton)
clearButton.isHidden = true
accessoryView.addSubview(nextTopButton)
}

Expand Down Expand Up @@ -155,118 +153,27 @@ private extension AddLinkView {
}
}

@objc func cancelButtonTapped() {
@objc
func cancelButtonTapped() {
linkEmbedTextField.text = ""
linkEmbedTextField.becomeFirstResponder()
}
}

// MARK: - Extension

extension AddLinkView: UITextFieldDelegate {

// MARK: - Timer Check

func textFieldDidBeginEditing(_ textField: UITextField) {
// 텍스트 필드에 입력이 시작될 때 호출되는 메서드
clearButton.isHidden = false
nextTopButton.backgroundColor = .black850
linkEmbedTextField.placeholder = nil

// 여기서 타이머를 시작하고, 0.5초 후에 텍스트를 확인 후 텍스트필드 에러 처리
if textField.text?.count ?? 0 > 1 {
startTimer()
}
}

func textField(_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {

// 입력이 발생할 때마다 호출되는 메서드
// 여기서 타이머를 재시작
restartTimer()
return true
}

func startTimer() {
// 0.5초 후에 checkTextField 메서드 호출
timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { [weak self] _ in
// URL 유효 여부 판단 후 처리
if let urlText = self?.linkEmbedTextField.text {
if (urlText.prefix(8) == "https://") || (urlText.prefix(7) == "http://") {
self?.resetError()
} else {
self?.isValidLinkError()
}
}
}
}

func restartTimer() {
// 타이머 재시작
stopTimer()
startTimer()
}

func stopTimer() {
// 타이머를 정지, 테두리 초기화
timer?.invalidate()
linkEmbedTextField.layer.borderColor = UIColor.clear.cgColor
}

// MARK: - Text Field Error

// 링크를 입력하는 텍스트필드가 비어 있을 경우 error 처리
func emptyError() {
linkEmbedTextField.layer.borderColor = UIColor.toasterError.cgColor
linkEmbedTextField.layer.borderWidth = 1

// Button 비활성화
nextTopButton.backgroundColor = .gray200
nextBottomButton.backgroundColor = .gray200
nextTopButton.isEnabled = false
nextBottomButton.isEnabled = false

errorLabel.text = "링크를 입력해주세요"
addSubview(errorLabel)
errorLabel.snp.makeConstraints {
$0.top.equalTo(linkEmbedTextField.snp.bottom).offset(6)
$0.leading.equalTo(linkEmbedTextField.snp.leading)
}
extension AddLinkView {
func isValidLinkError(_ message: String) {
errorLabel.text = message
errorLabel.isHidden = false
}

// 링크가 유효하지 않을 경우 error 처리
func isValidLinkError() {
linkEmbedTextField.layer.borderColor = UIColor.toasterError.cgColor
linkEmbedTextField.layer.borderWidth = 1

// Button 비활성화
nextTopButton.backgroundColor = .gray200
nextBottomButton.backgroundColor = .gray200
nextTopButton.isEnabled = false
nextBottomButton.isEnabled = false

errorLabel.text = "유효하지 않은 형식의 링크입니다"
addSubview(errorLabel)
errorLabel.snp.makeConstraints {
$0.top.equalTo(linkEmbedTextField.snp.bottom).offset(6)
$0.leading.equalTo(linkEmbedTextField.snp.leading)
}
errorLabel.isHidden = false
}

// 링크가 유효할 경우, error reset
func resetError() {
linkEmbedTextField.layer.borderColor = UIColor.clear.cgColor

// Button 활성화
nextTopButton.backgroundColor = .black850
nextBottomButton.backgroundColor = .black850
nextTopButton.isEnabled = true
nextBottomButton.isEnabled = true

errorLabel.isHidden = true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,22 @@ final class AddLinkViewController: UIViewController {
private weak var delegate: AddLinkViewControllerPopDelegate?
private weak var urldelegate: SelectClipViewControllerDelegate?

// MARK: - UI Properties
// MARK: - UI Components

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

// MARK: - Life Cycle

override func viewDidLoad() {
super.viewDidLoad()

setupStyle()
setAddLinkVew()
setupAddLinkVew()
hideKeyboard()

setupBinding()
updateUI()
}

override func viewWillAppear(_ animated: Bool) {
Expand All @@ -56,19 +60,6 @@ final class AddLinkViewController: UIViewController {

navigationBarHidden(forHidden: false)
}

// MARK: - set up Add Link View

private func setAddLinkVew() {
view.addSubview(addLinkView)

addLinkView.snp.makeConstraints {
$0.edges.equalTo(view.safeAreaLayoutGuide)
}

addLinkView.nextBottomButton.addTarget(self, action: #selector(tappedNextBottomButton), for: .touchUpInside)
addLinkView.nextTopButton.addTarget(self, action: #selector(tappedNextBottomButton), for: .touchUpInside)
}
}

// MARK: - extension
Expand All @@ -92,6 +83,17 @@ private extension AddLinkViewController {
view.backgroundColor = .toasterBackground
}

func setupAddLinkVew() {
view.addSubview(addLinkView)

addLinkView.snp.makeConstraints {
$0.edges.equalTo(view.safeAreaLayoutGuide)
}

addLinkView.nextBottomButton.addTarget(self, action: #selector(tappedNextBottomButton), for: .touchUpInside)
addLinkView.nextTopButton.addTarget(self, action: #selector(tappedNextBottomButton), for: .touchUpInside)
}

func setupNavigationBar() {
let type: ToasterNavigationType = ToasterNavigationType(hasBackButton: false,
hasRightButton: true,
Expand Down Expand Up @@ -123,16 +125,40 @@ private extension AddLinkViewController {
}

@objc func tappedNextBottomButton() {
if (addLinkView.linkEmbedTextField.text?.count ?? 0) < 1 {
addLinkView.emptyError()
let selectClipViewController = SelectClipViewController()
selectClipViewController.linkURL = addLinkView.linkEmbedTextField.text ?? ""
selectClipViewController.delegate = self
self.navigationController?.pushViewController(selectClipViewController, animated: true)
}

}

// 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

if let errorMessage = viewModel.outputs.linkEffectivenessMessage {
addLinkView.isValidLinkError(errorMessage)
} else {
let selectClipViewController = SelectClipViewController()
selectClipViewController.linkURL = addLinkView.linkEmbedTextField.text ?? ""
selectClipViewController.delegate = self
self.navigationController?.pushViewController(selectClipViewController, animated: true)
addLinkView.resetError()
}
}

}

extension AddLinkViewController: SaveLinkButtonDelegate {
Expand Down
Loading

0 comments on commit bb0bacf

Please sign in to comment.