---
# System prepended metadata

title: 20220126 iOS 일일 개발일지
tags: [develop]

---

20220126 iOS 일일 개발일지
===

###### tags: `develop`
[TOC]

## 아키텍쳐
                
![layer_1](https://i.imgur.com/MmUWhiz.png)

![layer_2](https://i.imgur.com/on6dXjR.png)

## 개발

### Clean Architecture 적용과 관련해 구조적인 흐름을 동일하게 가져가기

현재 프로젝트가 가진 구조
ViewController - ViewModel - Usecase - Repository - service -> Entity의 구조로 진행 중임.

Service Layer의 위치를 어디에 둘지에 대한 고민에 대해 현재는 Repository 내부에서 의존 관계를 가지도록 설계하였음.
-> 실질적인 API 통신은 Repository에서 이뤄지고 있으며, 구현해야하는 service는 현재는 네트워크 서비스만 있는 상태.

User Login State에 따라 LoginView가 뜰 수 있도록 해야 함.

TabBarController에서 해당 부분에 대한 점검이 필요함.
하지만, TabBarController 내부적으로 모두 Navigation Controller에 의해 감싸진 상태

```swift=
if let naviVC = viewController as? UINavigationController,
   (naviVC.viewControllers.first as? MyPageViewController != nil) && isLogout { }
```

NavigationController에 대한 형 변환 및 특정 타입이 맞는지 점검을 진행했음.

<details>
<summary>해당 Delegate 메소드 전체 코드</summary>
<div markdown="1">

```swift=
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
    // Test를 위한 변수 선언
    let isLogout = true
        
    if let naviVC = viewController as? UINavigationController,
           (naviVC.viewControllers.first as? MyPageViewController != nil) && isLogout {
        let loginViewController = SigninViewController()
        let navigationViewController = UINavigationController(rootViewController: loginViewController)
        navigationViewController.modalPresentationStyle = .fullScreen
        tabBarController.present(navigationViewController, animated: true, completion: nil)
        
        return false
    }
    return true
}
```
</div>
</details>


<details>
<summary> clean architecture 적용을 위한 작성 중 코드</summary>
<div markdown="1">

```swift=    
struct UserDTO: Decodable { }

protocol UserNetwork {
    func confirmUser(email: String, password: String, completion: @escaping (Result<UserDTO, Error>) -> Void)
}


final class DefaultNetworkService {
    static let shared = DefaultNetworkService()
    
    private init() { }
    
    func
}

extension DefaultNetworkService: UserNetwork {
    func confirmUser(email: String, password: String, completion: @escaping (Result<UserDTO, Error>) -> Void) {
        
    }
}

// MARK: Repository
protocol UserRepositoryInterface {
    func confirm(userEmail: String, userPassword: String, completion: @escaping (Result<User, Error>) -> Void)
}

final class UserRepository: UserRepositoryInterface {
    private let service: UserNetwork
    
    init(_ service: UserNetwork) {
        self.service = service
    }
    
    func confirm(userEmail: String, userPassword: String, completion: @escaping (Result<User, Error>) -> Void) {
        self.service.confirmUser(email: userEmail, password: userPassword) { result in
            switch result {
            case .success(let userDTO):
                completion(.success(userDTO.toDomain()))
            case .failure()
            }
        }
    }
}

// MARK: UseCase
protocol UserUseCaseInterface {
    func confirm(userEmail: String, userPassword: String, completion: @escaping (Result<User, Error>) -> Void)
}

final class UserUseCase: UserUseCaseInterface {
    private let repository: UserRepositoryInterface
    
    init(_ userRepository: UserRepositoryInterface) {
        self.repository = userRepository
    }
    
    func confirm(userEmail: String, userPassword: String, completion: @escaping (Result<User, Error>) -> Void) {
        repository.confirm(userEmail: userEmail, userPassword: userPassword) { <#Result<User, Error>#> in
            <#code#>
        }
    }
}


protocol LoginViewModelProperty {
    var usecase: UserUseCaseInterface { get }
    var emailMessage: Observable<String> { get set }
    var passwordMessage: Observable<String> { get set }
}

protocol LoginViewModelValidatable {
    func validateEmail(_ email: String)
    func validatePassword(_ password: String)
}

protocol LoginViewModel: LoginViewModelProperty, LoginViewModelValidatable {
    func confirmUser(email: String, password: String, completion: @escaping (Result<User, Error>) -> Void)
}

final class DefaultLoginViewModel: LoginViewModel {
    var usecase: UserUseCaseInterface
    var emailMessage: Observable<String> = Observable("")
    var passwordMessage: Observable<String> = Observable("")
    var isLoginAvailable: Observable<Bool> = Observable(false)
    
    init(usecase: UserUseCaseInterface = ) {
        self.usecase = usecase
    }
    
    func validateEmail(_ email: String) {
        if ValidationHelper.validate(email: email) {
            self.emailMessage.value = ValidationHelper.State.valid.description
        } else {
            self.emailMessage.value = ValidationHelper.State.emailFormatInvalid.description
        }
    }
    
    func validatePassword(_ password: String) {
        if ValidationHelper.validate(password: password) {
            self.passwordMessage.value = ValidationHelper.State.valid.description
        } else {
            self.passwordMessage.value = ValidationHelper.State.passwordFormatInvalid.description
        }
    }
    
    func confirmUser(email: String, password: String, completion: @escaping (Result<User, Error>) -> Void) {
//        usecase.confirm(userEmail: email, userPassword: password) { [weak self] result in
//            switch result {
//            case .success(let user):
//                completion(.success(user))
//            case .failure(<#T##Error#>)
//            }
    }
}

final class LoginViewController: BaseDIViewController<LoginViewModel> {
    
    override init(_ viewModel: LoginViewModel = DefaultLoginViewModel(usecase: <#UserUseCaseInterface#>)) {
        super.init(viewModel)
    }
}

```
                
</div>
</details>


<details>
<summary> ItemView, ViewController로부터 View를 분리(ItemView.swift)
</summary>
<div markdown="1">
        
```swift= 
//
//  ItemView.swift
//  Cream
//
//  Created by wankikim-MN on 2022/01/19.
//

import UIKit
import SnapKit

class ItemView: UIView {
    lazy var ItemInfoListView: UICollectionView = {
        let cv = UICollectionView(frame: .zero,
                                  collectionViewLayout: configureCollectionViewLayout())
        return cv
    }()

    lazy var buyButton: TradeButton = {
        let button = TradeButton(tradeType: .buy)
        return button
    }()

    lazy var sellButton: TradeButton = {
        let button = TradeButton(tradeType: .sell)
        return button
    }()

    lazy var wishButton: VerticalButton = {
        let button = VerticalButton.init(frame: .zero)
        button.imageView?.image = UIImage(systemName: "bookmark")
        button.titleLabel?.text = "찜 목록 개수"
        return button
    }()

    lazy var tradeStackView: UIStackView = {
        let stackView = UIStackView(arrangedSubviews: [buyButton, sellButton])
        stackView.axis = .horizontal
        stackView.distribution = .fillEqually
        stackView.spacing = 5
        return stackView
    }()

    lazy var containerStackView: UIStackView = {
        let stackView = UIStackView(arrangedSubviews: [wishButton, tradeStackView])
        stackView.axis = .horizontal
        stackView.spacing = 10
        return stackView
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        applyViewSettings()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        applyViewSettings()
    }
}

// MARK: CollectionViewLayout
extension ItemView {
    func configureCollectionViewLayout() -> UICollectionViewCompositionalLayout {
        return UICollectionViewCompositionalLayout { (section, env) -> NSCollectionLayoutSection? in
            switch section {
            case 0:
                return self.configureImageSectionLayout()
            case 1:
                return self.configureItemInfoSectionLayout()
            case 2:
                return self.configureReleaseSectionLayout()
            case 3:
                return self.configureDeliverySectionLayout()
            case 4:
                return self.configureAdvertiseSectionLayout()
            case 5:
                return self.configurePriceChartSectionLayout()
            default:
                return self.configureSimilarItemSectionLayout()
            }
        }
    }

    private func configureImageSectionLayout() -> NSCollectionLayoutSection {
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
                                              heightDimension: .fractionalWidth(1))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        item.contentInsets.bottom = 15

        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
                                               heightDimension: .fractionalWidth(1))

        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])

        group.contentInsets = .init(top: 0, leading: 0, bottom: 0, trailing: 0)

        let section = NSCollectionLayoutSection(group: group)
        section.orthogonalScrollingBehavior = .groupPaging

        return section
    }

    private func configureItemInfoSectionLayout() -> NSCollectionLayoutSection {
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
                                              heightDimension: .fractionalWidth(0.52))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        item.contentInsets.bottom = 15

        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
                                               heightDimension: .fractionalWidth(0.52))

        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])

        group.contentInsets = .init(top: 0, leading: 0, bottom: 0, trailing: 0)

        let section = NSCollectionLayoutSection(group: group)
        section.orthogonalScrollingBehavior = .groupPaging

        return section
    }

    private func configureReleaseSectionLayout() -> NSCollectionLayoutSection {
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.4),
                                              heightDimension: .fractionalWidth(0.3))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        item.contentInsets.bottom = 15

        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
                                               heightDimension: .fractionalWidth(0.35))
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
        group.contentInsets = .init(top: 0, leading: 0, bottom: 0, trailing: 0)

        let section = NSCollectionLayoutSection(group: group)
        section.orthogonalScrollingBehavior = .groupPaging

        return section
    }

    private func configureDeliverySectionLayout() -> NSCollectionLayoutSection {
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
                                              heightDimension: .fractionalWidth(0.3))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        item.contentInsets.bottom = 0

        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
                                               heightDimension: .fractionalWidth(0.3))

        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
        group.contentInsets = .init(top: 0, leading: 0, bottom: 0, trailing: 0)

        let section = NSCollectionLayoutSection(group: group)
        section.orthogonalScrollingBehavior = .groupPaging

        return section
    }

    private func configureAdvertiseSectionLayout() -> NSCollectionLayoutSection {
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
                                              heightDimension: .fractionalWidth(0.256))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        item.contentInsets.bottom = 0

        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
                                               heightDimension: .fractionalWidth(0.256))

        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
        group.contentInsets = .init(top: 0, leading: 0, bottom: 0, trailing: 0)

        let section = NSCollectionLayoutSection(group: group)
        section.orthogonalScrollingBehavior = .groupPaging

        return section
    }

    private func configurePriceChartSectionLayout() -> NSCollectionLayoutSection {
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
                                              heightDimension: .fractionalWidth(1.2))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        item.contentInsets.bottom = 15

        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
                                               heightDimension: .fractionalWidth(1.2))

        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])

        group.contentInsets = .init(top: 0, leading: 0, bottom: 0, trailing: 0)

        let section = NSCollectionLayoutSection(group: group)
        section.orthogonalScrollingBehavior = .groupPaging

        return section
    }

    private func configureSimilarItemSectionLayout() -> NSCollectionLayoutSection {
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.4),
                                              heightDimension: .fractionalHeight(0.464))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        item.contentInsets = .init(top: 0, leading: 0, bottom: 5, trailing: 5)
        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
                                               heightDimension: .estimated(464))
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
        let section = NSCollectionLayoutSection(group: group)
        section.orthogonalScrollingBehavior = .groupPaging

        section.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 5, bottom: 20, trailing: 0)
        section.boundarySupplementaryItems = [
            NSCollectionLayoutBoundarySupplementaryItem(layoutSize: .init(widthDimension: .fractionalWidth(1),
                                                                          heightDimension: .estimated(40)),
                                                        elementKind: UICollectionView.elementKindSectionHeader,
                                                        alignment: .topLeading)
        ]
        return section
    }
}

                
extension ItemView: ViewConfiguration {
    func buildHierarchy() {
        self.addSubviews(ItemInfoListView, containerStackView)
    }

    func setupConstraints() {
        ItemInfoListView.snp.makeConstraints {
//            $0.top.equalTo(self.snp.top)
//            $0.leading.equalTo(self.snp.leading)
//            $0.trailing.equalTo(self.snp.trailing)
            $0.top.leading.trailing.equalToSuperview()
            $0.bottom.equalTo(self.containerStackView.snp.top)
        }

        containerStackView.snp.makeConstraints {
            $0.leading.trailing.bottom.equalToSuperview()
        }
    }

    func viewConfigure() {
        collectionViewConfigure()
    }

    func collectionViewConfigure() {
        ItemInfoListView.register(ItemInfoCell.self, forCellWithReuseIdentifier: ItemInfoCell.reuseIdentifier)
        ItemInfoListView.register(ReleaseInfoCell.self, forCellWithReuseIdentifier: ReleaseInfoCell.reuseIdentifier)
        ItemInfoListView.register(ShopBannerCell.self, forCellWithReuseIdentifier: ShopBannerCell.reuseIdentifier)
        ItemInfoListView.register(HomeViewItemCell.self, forCellWithReuseIdentifier: HomeViewItemCell.reuseIdentifier)
    }
}

```


</div>
</details>

<br>
                
> Network Layer 고려사항.
                
**Clean Architecture**을 활용할 경우, 
DIP를 준수하여 network에 대해서도 testable하도록 설계가 가능해보임.

고려사항
- Network를 기능별로 나누어 사용하기 편리하도록 설계하기.

참고 링크: https://ios-development.tistory.com/712
