개발하는 동글 :]

[TIL],[UIKit],[Controller 상속해서 사용하기] 본문

카테고리 없음

[TIL],[UIKit],[Controller 상속해서 사용하기]

동글하다 2023. 10. 24. 00:39

1.  문제 상황

위의 이미지처럼 다양한 다양한 화면에서 비슷한 화면이 자주 사용된다. 그렇기에 반복되는 작업을 줄이고자 하였다.

2. 시도한 방법

2.1 LockScreenView 구현

더보기
//
//  LockScreenView.swift
//  FinalTodo
//
//  Created by SeoJunYoung on 10/23/23.
//

import UIKit

class LockScreenView: UIView {

    let titleLabel: UILabel = {
        let label = UILabel()
        label.font = .preferredFont(forTextStyle: .title1)
        return label
    }()
    
    lazy var passwordCollectionView: UICollectionView = {
        let flowLayout = UICollectionViewFlowLayout()
        flowLayout.itemSize = .init(width: Constant.screenWidth * 0.05, height: Constant.screenWidth * 0.05)
        flowLayout.minimumLineSpacing = Constant.defaultPadding
        flowLayout.scrollDirection = .horizontal
        let view = UICollectionView(frame: .zero, collectionViewLayout: flowLayout)
        view.backgroundColor = .clear
        return view
    }()
    
    lazy var passwordInfoLabel: UILabel = {
        let label = UILabel()
        label.font = .preferredFont(forTextStyle: .body)
        label.textColor = .red
        label.textAlignment = .center
        label.alpha = 0
        return label
    }()
    
    lazy var numsCollectionView: UICollectionView = {
        let flowLayout = UICollectionViewFlowLayout()
        let spacing = Constant.defaultPadding
        flowLayout.minimumLineSpacing = spacing
        
        let width = (Constant.screenWidth - (Constant.defaultPadding * 6) - (spacing * 4)) / 3
        flowLayout.itemSize = .init(width: width, height: width)
        let view = UICollectionView(frame: .zero, collectionViewLayout: flowLayout)
        view.backgroundColor = .clear
        return view
    }()
    
    init() {
        super.init(frame: CGRect.zero)
        setUp()
    }
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

private extension LockScreenView {
    // MARK: - SetUp
    
    func setUp() {
        setUpPasswordLabel()
        setUpPasswordCollectionView()
        setUpPasswordInfoLabel()
        setUpNumsCollectionView()
    }
    
    func setUpPasswordLabel() {
        self.addSubview(titleLabel)
        titleLabel.snp.makeConstraints { make in
            make.top.equalToSuperview().offset(Constant.screenHeight * 0.15)
            make.centerX.equalToSuperview()
        }
    }
    
    func setUpPasswordInfoLabel() {
        self.addSubview(passwordInfoLabel)
        passwordInfoLabel.snp.makeConstraints { make in
            make.top.equalTo(passwordCollectionView.snp.bottom).offset(Constant.defaultPadding)
            make.left.right.equalToSuperview().inset(Constant.defaultPadding)
        }
    }
    
    func setUpPasswordCollectionView() {
        self.addSubview(passwordCollectionView)
        passwordCollectionView.snp.makeConstraints { make in
            make.top.equalTo(titleLabel.snp.bottom).offset(Constant.defaultPadding)
            make.left.right.equalToSuperview()
            make.height.equalTo(Constant.screenWidth * 0.05)
        }
        passwordCollectionView.register(
            LockScreenPasswordCollectionViewCell.self, forCellWithReuseIdentifier: LockScreenPasswordCollectionViewCell.identifier
        )
    }
    
    func setUpNumsCollectionView() {
        self.addSubview(numsCollectionView)
        numsCollectionView.snp.makeConstraints { make in
            make.top.equalTo(passwordCollectionView.snp.bottom).offset(Constant.screenHeight * 0.1)
            make.left.right.equalToSuperview().inset(Constant.defaultPadding * 3)
            make.bottom.equalToSuperview()
        }
        numsCollectionView.register(
            LockScreenNumCollectionViewCell.self, forCellWithReuseIdentifier: LockScreenNumCollectionViewCell.identifier
        )
    }
}
view에 관한 부분을 분리하여 재사용하는 방법을 선택하였다. 그런데 이 방법 또 한 공통적으로  작동하는 로직들을 반복해서 적어야 한다는 문제점이 있어 반복되는 로직들 또 한 재사용하는 방법을 생각하던 중 상속하여 사용하는 방법을 시도하였다.

2.2 LockController 구현

더보기
//
//  LockController.swift
//  FinalTodo
//
//  Created by SeoJunYoung on 10/23/23.
//

import Foundation
import UIKit

class LockController: UIViewController {
    
    enum PasswordShowType {
        case mismatch
        case different
    }
    
    let lockScreenView = LockScreenView()
    
    let userDefaultsManager = UserDefaultsManager()
    
    lazy var lockScreenPassword = userDefaultsManager.getPassword()
    
    let failCount: Observable<Int> = Observable(0)
    
    let userInPutPassword: Observable<String> = Observable("")
    
    let passwordCollectionviewSpacing = Constant.defaultPadding
    
    let passwordCollectionviewItemSize: CGSize = .init(
        width: Constant.screenWidth * 0.05,
        height: Constant.screenWidth * 0.05
    )
    
    let passwordLength:CGFloat = 4
    
    let numPadComposition = [
        "1", "2", "3",
        "4", "5", "6",
        "7", "8", "9",
        "AC","0", "C"
    ]
    
    let numPadCollectionviewSpacing = Constant.defaultPadding * 2
    
    lazy var numPadCollectionviewItemSize: CGSize = .init(
        width: (Constant.screenWidth - (Constant.defaultPadding * 6) - (numPadCollectionviewSpacing * 2)) / 3,
        height: (Constant.screenWidth - (Constant.defaultPadding * 6) - (numPadCollectionviewSpacing * 2)) / 3
    )

    deinit {
        print("[LockScreenViewController]: deinit")
    }
}

extension LockController {
    // MARK: - LifeCycle
    override func viewDidLoad() {
        super.viewDidLoad()
        setUp()
        bind()
    }
}

extension LockController {

    // MARK: - Bind
    
    @objc func bind() {
        userInPutPassword.bind { [weak self] inputData in
            guard let self = self else { return }
            lockScreenView.passwordInfoLabel.alpha = 0
            lockScreenView.passwordCollectionView.reloadData()
        }
    }
    // MARK: - SetUp

    func setUp() {
        view.backgroundColor = ColorManager.themeArray[0].backgroundColor
        view.addSubview(lockScreenView)
        lockScreenView.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }
        lockScreenView.numsCollectionView.delegate = self
        lockScreenView.numsCollectionView.dataSource = self
        lockScreenView.passwordCollectionView.delegate = self
        lockScreenView.passwordCollectionView.dataSource = self
    }
    
    // MARK: - Method
    
    func showPasswordMissMatch(type:PasswordShowType) {
        
        userInPutPassword.value = ""
        failCount.value += 1
        print("FailCount:",failCount.value)
        switch type {
        case .different:
            lockScreenView.passwordInfoLabel.text = "비밀번호가 일치하지 않습니다"
        case .mismatch:
            lockScreenView.passwordInfoLabel.text = "비밀번호를 확인해 주세요"
        }
        UIView.animate(withDuration: 1) {
            self.lockScreenView.passwordInfoLabel.alpha = 1
        }
        lockScreenView.passwordCollectionView.shake()
    }
}

extension LockController: UICollectionViewDelegate, UICollectionViewDataSource {
    // MARK: - extension UICollectionViewDelegate, UICollectionViewDataSource

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        if collectionView == lockScreenView.passwordCollectionView {
            return Int(passwordLength)
        } else {
            return numPadComposition.count
        }
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        if collectionView == lockScreenView.passwordCollectionView {
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: LockScreenPasswordCollectionViewCell.identifier, for: indexPath) as! LockScreenPasswordCollectionViewCell
            if indexPath.row < userInPutPassword.value.count {
                cell.bind(toggle: true)
            } else {
                cell.bind(toggle: false)
            }
            return cell
        } else {
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: LockScreenNumCollectionViewCell.identifier, for: indexPath) as! LockScreenNumCollectionViewCell
            cell.bind(title: numPadComposition[indexPath.row])
            return cell
        }
    }
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        if collectionView == lockScreenView.numsCollectionView {
            switch numPadComposition[indexPath.row] {
            case "AC":
                userInPutPassword.value = ""
            case "C":
                if userInPutPassword.value.count != 0 {
                    let _ = userInPutPassword.value.popLast()
                }
            default:
                if Int(passwordLength) > userInPutPassword.value.count {
                    userInPutPassword.value += numPadComposition[indexPath.row]
                }
            }
        }
    }
}

extension LockController: UICollectionViewDelegateFlowLayout {

    // MARK: - extension UICollectionViewDelegateFlowLayout

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        
        if collectionView == lockScreenView.passwordCollectionView {
            
            let totalCellWidth = passwordCollectionviewItemSize.width * passwordLength
            let totalSpacingWidth = passwordCollectionviewSpacing * (passwordLength - 1)
            let leftInset = (collectionView.bounds.width - CGFloat(totalCellWidth + totalSpacingWidth)) / 2
            let rightInset = leftInset
            return UIEdgeInsets(top: 0, left: leftInset, bottom: 0, right: rightInset)
        }
        return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
    }
}
기존의 로직들도 재사용하기 위해 공통적으로 사용되는 부분을 찾아 LockController에 정의하였고 다양한 화면에서 LockController를 상속받아 일부분의 로직만 수정하여 반복되는 작업을 줄일 수 있었다.