iOS 개발/번역기 앱(RxSwift)

번역기 앱 - 9. 번역할 Text 입력 View

Skillist 2022. 2. 1. 20:04
반응형

안녕하세요. Skillist입니다

 

아침에 창밖에 쌓인 눈을 보고 글 작성을 시작했는데, 날이 제법 어두워졌습니다.

하루종일 학습과 개발, 글작성을 하다보니, 밖에 나가서 놀고싶네요.

내일은 미세먼지도 좋고, 날도 안추우면 좋겠네요.

 

이번엔 Text 입력 View입니다.

label 1개와 버튼 1개, textView 1개를 사용합니다.

우측 상단의 버튼을 터치하면, hide되고, textView의 text가 리셋됩니다.

 

———————————————————————————————————————————————————

 

viewModel을 먼저 볼게요.

 

16라인 : 입력된 Text를 리셋하는 리셋 버튼 탭에 대한 Relay입니다.

17라인 : 입력된 Text에 대한 Relay입니다.

18라인 : 번역할 text가 변경되면, 가장 최근에 번역된 View를 hidden할 예정입니다. 이에 사용할 Relay입니다.

19라인 : 번역 버튼(엔터, done)을 터치하면, 번역 API에 request할겁니다. 이때 사용합니다.

22라인 : 이전에 개발한 선택된 언어가 변경되면, 좌측 상단에 표시할 언어도 변경해야 합니다. 이때 사용됩니다.

 

———————————————————————————————————————————————————

 

View를 보시죠. RxSwift 관련 코드인 바인드는 마지막에 보겠습니다.

View에서 사용할 label과 button, taxtView 입니다. 

 



view 기본 설정과, 스냅킷을 통해서 레이아웃 제약사항을 구성합니다.

특이점은 138라인에 height를 300으로 설정한다는 것입니다.

 

143라인 : 엔터키 입력 인식하는 로직입니다.

delegate를 통해서, 개행문자가 들어오면, 엔터키 입력됐다고 보시면 됩니다.

또한, 키보드를 입력을 종료하여 키보드 hide합니다.

delegate proxy를 사용하면 될텐데, 사용하지 않았습니다.

152라인 : 엔터키가 입력되면 viewModel의 translateButtonTap에 이벤트를 전달합니다.

 

57라인 : 언어가 변경되면, 언어 라벨의 text도 변경합니다.

65라인 : clear 버튼을 탭하면, viewModel의 clearButtonTap으로 바인드합니다.

69라인 : map을 통해서 입력된 text가 isEmpty인지에 대한 bool을 반환하여, clearButton의 isHidden에 바인드합니다.

76라인 : clear 버튼을 터치하면, 라벨의 text를 리셋합니다.

84라인 : text를 viewModel.inputText에 바인드합니다.

88라인 : 입력한 text가 변경되지 않으면 수행되지 않기위해 추가하였습니다.

92라인 : text가 변경되면 viewModel의 changedInputText에 바인드합니다.

 

이벤트를 주고 받고에 대한 흐름을 보세요.

개발할땐, 복잡했는데, 글을 통해서 한번더 정리하며 머릿속이 깔끔해지네요.

 

———————————————————————————————————————————————————

 

여러분! 오늘도 열심히 공부 해봤습니다.

열심히 공부하는 열정에 정말로 리스펙합니다.

우리 모두 좋은일만 가득하길 바래요.

2월 1일 공부는 끝!

 

잘못되거나 부족한 내용 등, 피드백 감사합니다!

 

https://github.com/DeveloperSkillist/TranstorKing

 

GitHub - DeveloperSkillist/TranstorKing

Contribute to DeveloperSkillist/TranstorKing development by creating an account on GitHub.

github.com

 

 

↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓  전체 코드  ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

import Foundation
import RxSwift
import RxCocoa

struct SourceTextInputViewModel {
    let disposeBag = DisposeBag()
    
    //view -> viewModel
    let clearButtonTap = PublishRelay<Void>()
    let inputText = PublishRelay<String>()
    let changedInputText = PublishRelay<Void>()
    let translateButtonTap = PublishSubject<Void>()
    
    //viewModel -> view
    let selectedLanguage = PublishRelay<Language>()
}
import UIKit
import SnapKit
import RxSwift
import RxCocoa

class SourceTextInputView: UIView {
    let disposeBag = DisposeBag()
    
    private lazy var languageLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 17, weight: .bold)
        label.textColor = .systemGray
        label.text = Language.ko.title
        return label
    }()
    
    private lazy var clearButton: UIButton = {
        let button = UIButton()
        button.setImage(UIImage(systemName: "xmark"), for: .normal)
        button.tintColor = .systemGray
        button.isHidden = true
        return button
    }()
    
    private lazy var inputTextView: UITextView = {
        let textView = UITextView()
        textView.backgroundColor = .systemBackground
        textView.font = .systemFont(ofSize: 20)
        textView.returnKeyType = .done
        textView.delegate = self
        return textView
    }()
    
    override init(frame: CGRect) {
        super.init(frame: .zero)
        
        attribute()
        layout()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func bind(_ viewModel: SourceTextInputViewModel) {
        //selected
        viewModel.selectedLanguage
            .map {
                $0.title
            }
            .bind(to: languageLabel.rx.text)
            .disposed(by: disposeBag)
        
        //clearButton
        clearButton.rx.tap
            .bind(to: viewModel.clearButtonTap)
            .disposed(by: disposeBag)
        
        viewModel.inputText
            .map {
                return $0.isEmpty
            }
            .bind(to: clearButton.rx.isHidden)
            .disposed(by: disposeBag)
        
        viewModel.clearButtonTap
            .map {
                ""
            }
            .bind(to: inputTextView.rx.text)
            .disposed(by: disposeBag)
        
        //inputTextView
        inputTextView.rx.text
            .map {
                $0 ?? ""
            }
            .distinctUntilChanged()
            .bind(to: viewModel.inputText)
            .disposed(by: disposeBag)
        
        inputTextView.rx.didEndEditing
            .asObservable()
            .bind(to: viewModel.translateButtonTap)
            .disposed(by: disposeBag)
        
        inputTextView.rx.didChange
            .asObservable()
            .bind(to: viewModel.changedInputText)
            .disposed(by: disposeBag)
    }
    
    private func attribute() {
        self.backgroundColor = .systemBackground
        
        self.layer.cornerRadius = 10
        self.clipsToBounds = true
    }
    
    private func layout() {
        
        self.clipsToBounds = true
        
        [
            languageLabel,
            clearButton,
            inputTextView
        ].forEach {
            self.addSubview($0)
        }
        
        languageLabel.snp.makeConstraints {
            $0.top.equalToSuperview().inset(16)
            $0.leading.equalToSuperview().inset(20)
        }
        
        clearButton.snp.makeConstraints {
            $0.top.bottom.equalTo(languageLabel)
            $0.trailing.equalToSuperview().inset(16)
            $0.width.height.equalTo(languageLabel.snp.height)
        }
        
        inputTextView.snp.makeConstraints {
            $0.top.equalTo(languageLabel.snp.bottom).offset(8)
//            $0.leading.equalToSuperview().inset(16)
            $0.leading.equalTo(languageLabel)
            $0.trailing.equalTo(clearButton)
            $0.bottom.equalToSuperview().inset(16)
        }
        
        self.snp.makeConstraints {
            $0.height.equalTo(300)
        }
    }
}

extension SourceTextInputView: UITextViewDelegate {
    func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
        
        let isEnterKey = (text == "\n")
        if isEnterKey {
            endEditing(true)
        }
        return true
    }
}
반응형