구현 결과

 

구현 코드

https://github.com/ryr0121/UIKitPractice/tree/main/StackViewPractice

 

사용한 라이브러리

Snapkit, Then

 

구현 과정

구현에 참고한 이미지

1. 구현 영역 분리

- 모서리가 둥근 전체 컨테이너 역할의 UIView

- 좌측 상단의 도시명, 현재 기온을 포함한 UIStackView (수직 방향)

- 우측 상단의 구름 이미지, 날씨, 최고/최저 기온을 포함한 UIStackView (수직 방향)

- 하단의 각 시간대별 날씨를 포함한 UIStackView (수평 방향)

 

 

2. 사용될 UI 컴포넌트 정의

2-1. 모서리가 둥근 전체 컨테이너 역할의 UIView 정의

lazy var backView = UIView().then {
    $0.backgroundColor = .gray
    $0.layer.cornerRadius = 20
    $0.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(didTapWeatherView)))
}

 

2-2. 좌측 상단의 도시명, 현재 기온을 포함한 수직 UIStackView

 

lazy var leftTitleLabel1 = getLabelView(titleStr: "서울특별시", fontSize: 20.0, fontWeight: .bold, fontColor: .white)
lazy var leftTitleLabel2 = getLabelView(titleStr: "9°", fontSize: 40.0, fontWeight: .medium, fontColor: .white)

lazy var leftStackView = UIStackView().then {
    $0.backgroundColor = .green
    
    // 스택 요소로 포함할 subView들을 선언
    let stackView = UIStackView(arrangedSubviews: [leftTitleLabel1, leftTitleLabel2])
    
    $0.axis = .vertical		// 수직 방향으로 스택을 구성
    $0.distribution = .fill	// 요소들이 스택을 채울 수 있게 분포되도록 구성
    $0.alignment = .leading	// 왼쪽 끝에 붙어서 요소가 나열되도록 구성
    $0.spacing = 0		// 요소 간의 간격 0
}

 
// helper methods - 컨테이너 뷰를 가진 UILabel 컴포넌트를 반환
func getLabelView(titleStr: String, fontSize: Double, fontWeight: UIFont.Weight, fontColor: UIColor) -> UIView {
    let titleLabel = UILabel().then {
        $0.text = titleStr
        $0.font = .systemFont(ofSize: fontSize, weight: fontWeight)
        $0.textColor = fontColor
    }
    let containerView = UIView().then {
        $0.backgroundColor = .systemPink
        $0.backgroundColor = .clear
    }

    containerView.addSubview(titleLabel)
    titleLabel.snp.makeConstraints { make in
        make.top.bottom.leading.trailing.equalToSuperview()
    }
    return containerView
}

 

2-3. 우측 상단의 구름 이미지, 날씨, 최고/최저 기온을 포함한 수직 UIStackView

let cloudImgView = UIImageView(image: UIImage(systemName: "cloud.fill")).then {
    $0.tintColor = .white
}

// getLabelView는 상단 helper method 참조
lazy var rightTitleLabel1 = getLabelView(titleStr: "대체로 흐림", fontSize: 18.0, fontWeight: .medium, fontColor: .white)
lazy var rightTitleLabel2 = getLabelView(titleStr: "최고: 21° 최저 7°", fontSize: 18.0, fontWeight: .medium, fontColor: .white)
    
lazy var rightStackView = UIStackView().then {
    $0.backgroundColor = .blue
    let stackView = UIStackView(arrangedSubviews: [cloudImgView, rightTitleLabel1, rightTitleLabel2])
    $0.axis = .vertical
    $0.distribution = .fill
    $0.alignment = .trailing	// 요소들이 오른쪽 끝에 붙어 나열될 수 있도록 구성
    $0.spacing = 3
}

 

좌우측 상단에 스택뷰를 추가한 후

 

2-4. - 하단의 각 시간대별 날씨를 포함한 수평 UIStackView 

let weatherWithTimeList = [
    ["오전 8시", "10°"],
    ["오전 9시", "12°"],
    ["오전 10시", "15°"],
    ["오전 11시", "17°"],
    ["오후 12시", "19°"],
    ["오후 1시", "20°"],
]

lazy var weatherViews: [UIView] = []

// helper method - 시간 및 기온 정보 배열을 토대로 하단 수평 스택뷰의 요소로 쓰이는 UIView 반환
// viewDidLoad 내에서 호출하여 weatherViews 배열 구성
func configureWeatherOfTime() {
    weatherWithTimeList.map { info in
        weatherViews.append(getWeatherViewOfTime(timeStr: info[0], temper: info[1]))
    }
}

lazy var bottomStackView = UIStackView().then {
    let stackView = UIStackView(arrangedSubviews: weatherViews)
    $0.axis = .horizontal	// 수평 방향으로 요소가 나열되도록 지정
    $0.distribution = .fillEqually	// 모든 요소가 스택 내에서 동등한 크기를 가지도록 지정
    $0.alignment = .leading
    $0.spacing = 5
}

하단 스택뷰를 추가한 후

 

 

3. 레이아웃 구성

private func setLayouts() {
    // add views
    self.view.addSubview(backView)

	// 전체 컨테이너 뷰에 좌측 상단, 우측 상단, 하단 스택뷰를 추가
    backView.addSubview(leftStackView)
    backView.addSubview(rightStackView)
    backView.addSubview(bottomStackView)

    // add views in StackView
    // 좌측 상단 스택뷰 내에 요소로 사용될 sub view들을 추가
    leftStackView.addArrangedSubview(leftTitleLabel1)
    leftStackView.addArrangedSubview(leftTitleLabel2)

    // 우측 상단 스택뷰 내에 요소로 사용될 sub view들을 추가
    rightStackView.addArrangedSubview(cloudImgView)
    rightStackView.addArrangedSubview(rightTitleLabel1)
    rightStackView.addArrangedSubview(rightTitleLabel2)

    // 하단 스택뷰 내에 요소로 사용될 sub view들을 추가
    weatherViews.map { view in
        bottomStackView.addArrangedSubview(view)
    }

    // set constraints
    backView.snp.makeConstraints { make in
        make.top.equalToSuperview().offset(130)
        make.leading.equalToSuperview().offset(20)
        make.trailing.equalToSuperview().inset(20)
    }

    leftStackView.snp.makeConstraints { make in
        make.leading.top.equalToSuperview().offset(15)
    }

    cloudImgView.snp.makeConstraints { make in
        make.top.equalToSuperview()
    }
    rightStackView.snp.makeConstraints { make in
        make.top.trailing.equalToSuperview().inset(15)
        make.bottom.equalTo(leftStackView.snp.bottom)
    }
    bottomStackView.snp.makeConstraints { make in
        make.top.equalTo(leftStackView.snp.bottom).offset(30)
        make.leading.trailing.equalToSuperview().inset(10)
        make.bottom.equalToSuperview().inset(15)
    }
}

 

4. 추가) 수직 스택뷰 요소 간 spacing 달리 주기

// 구름 이미지 UIImageView를 감싸는 컨테이너 뷰 정의
let cloudImgContainerView = UIView().then {
    $0.backgroundColor = .clear
}

let cloudImgView = UIImageView(image: UIImage(systemName: "cloud.fill")).then {
    $0.tintColor = .white
    $0.contentMode = .scaleAspectFill
}

// 레이아웃 구성 변경
cloudImgView.snp.makeConstraints { make in
    make.top.bottom.leading.trailing.equalToSuperview()
}

// 구름 이미지 컴포넌트 하단 요소에 대해 top 방향으로 원하는 만큼의 여백값을 추가
rightTitleLabel1.snp.makeConstraints { make in
    make.top.equalTo(cloudImgView.snp.bottom).offset(20)
}

 

5. 추가) 전체 컨테이너 영역 내에 대한 탭 이벤트 정의

lazy var backView = UIView().then {
    $0.backgroundColor = .gray
    $0.layer.cornerRadius = 20
    
    // 탭 제스처를 감지하는 객체를 추가
    $0.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(didTapWeatherView)))
}

@objc func didTapWeatherView() {
    let detailVC = WeatherDetailViewController()
    self.navigationController?.pushViewController(detailVC, animated: true)
}

*참고*

SceneDelegate.swift 파일 내 willConnectTo 메소드

코드 베이스로 UIKit을 사용할 경우,

가장 처음 시작점이 되는 ViewController 내에서 NavigationController를 사용하기 위해서는 

해당 ViewController가 UINavigationContoller를 가질 수 있도록 정의해주어야 함

 

 

 

 

+ Recent posts