기술스택 선정이유

https://github.com/boostcampwm2023/iOS07-traveline/wiki/[iOS]-단방향-플로우-ViewModel-구현기

디자인 시스템 구현

<aside> 💡

디자인 시스템 구현

  1. 이미지

  2. 컬러셋 추가

    https://velog.io/@niro/SOPT-앱잼-Xcode-15-에서-Image-Color-에-접근하는-새로운-방법

  3. 폰트 추가

  4. 텍스트 익스텐션 </aside>

네트워크 레이어

https://github.com/pcsoyeon/Network

MVVM

https://hyun-je.github.io/ios/2019/04/13/one_way_data_stream_viewmodel.html

https://velog.io/@wnsxor1993/MVVM-Clean-Architecture-정리 → 다이어그램

메인/상세화면

https://nsios.tistory.com/203

UIStackView는 기본적으로 한 방향으로만 배치됩니다. 하지만 해시태그가 여러 줄로 넘어가는 레이아웃을 구현하려면 UIStackView를 수직 스택뷰와 수평 스택뷰를 조합하거나, **"줄바꿈이 가능한 동적 레이아웃"**을 위해 UILabel이나 UICollectionView 대신 UIStackView 안에 컨트롤을 구성할 수 있습니다.

두 줄 이상의 레이아웃 구현 (스택뷰 활용)

핵심 구현 아이디어:

이 방식을 통해 태그가 화면 너비를 넘어가면, 새로운 수평 스택뷰를 추가하여 새로운 줄을 시작할 수 있습니다.


구현 코드

import UIKit

class ViewController: UIViewController {

    let verticalStackView = UIStackView()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white

        // 1. Vertical StackView 설정
        verticalStackView.axis = .vertical
        verticalStackView.spacing = 8
        verticalStackView.alignment = .leading
        verticalStackView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(verticalStackView)

        // 2. Vertical StackView 레이아웃
        NSLayoutConstraint.activate([
            verticalStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
            verticalStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
            verticalStackView.topAnchor.constraint(equalTo: view.topAnchor, constant: 100)
        ])

        // 3. 태그 데이터 추가
        let tags = ["핑구", "캐릭터", "펭귄", "D-5", "굿즈", "이벤트", "여의도", "더현대서울"] // 서버 데이터
        addTagsToVerticalStackView(tags: tags)
    }

    private func addTagsToVerticalStackView(tags: [String]) {
        // 한 줄에 들어갈 태그를 관리할 Horizontal StackView
        var currentHorizontalStackView = createHorizontalStackView()
        verticalStackView.addArrangedSubview(currentHorizontalStackView)

        var currentRowWidth: CGFloat = 0
        let maxWidth = view.bounds.width - 32 // 화면 너비에서 양쪽 마진(16씩)을 뺌

        for tag in tags {
            let tagLabel = createTagLabel(text: tag)
            let tagWidth = tagLabel.intrinsicContentSize.width + 16 // 태그의 실제 너비 + padding(좌우 8씩)

            // 새 줄로 넘어가야 할 경우
            if currentRowWidth + tagWidth > maxWidth {
                currentHorizontalStackView = createHorizontalStackView()
                verticalStackView.addArrangedSubview(currentHorizontalStackView)
                currentRowWidth = 0
            }

            // 현재 줄에 태그 추가
            currentHorizontalStackView.addArrangedSubview(tagLabel)
            currentRowWidth += tagWidth + 8 // 태그 간의 간격 추가
        }
    }

    private func createHorizontalStackView() -> UIStackView {
        let stackView = UIStackView()
        stackView.axis = .horizontal
        stackView.spacing = 8
        stackView.alignment = .leading
        stackView.translatesAutoresizingMaskIntoConstraints = false
        return stackView
    }

    private func createTagLabel(text: String) -> UILabel {
        let label = UILabel()
        label.text = text
        label.textColor = .white
        label.font = UIFont.systemFont(ofSize: 14, weight: .bold)
        label.textAlignment = .center
        label.backgroundColor = UIColor.systemBlue
        label.layer.cornerRadius = 15
        label.layer.masksToBounds = true
        label.translatesAutoresizingMaskIntoConstraints = false
        label.heightAnchor.constraint(equalToConstant: 30).isActive = true
        label.widthAnchor.constraint(equalToConstant: text.size(withAttributes: [.font: UIFont.systemFont(ofSize: 14)]).width + 20).isActive = true
        return label
    }
}