구현 코드
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)
}
*참고*
코드 베이스로 UIKit을 사용할 경우,
가장 처음 시작점이 되는 ViewController 내에서 NavigationController를 사용하기 위해서는
해당 ViewController가 UINavigationContoller를 가질 수 있도록 정의해주어야 함
'iOS > UIKit' 카테고리의 다른 글
[iOS] Storyboard 없이 코드베이스로 프로젝트 설정하기 (0) | 2024.03.07 |
---|---|
[iOS] "SDK does not contain 'libarclite' ..." 트러블 슈팅 (0) | 2024.03.07 |
[iOS] Storyboard로 커스텀 Cell을 가진 CollectionView 만들기 (0) | 2023.10.12 |
[Storyboard] 커스텀 Cell을 가진 TableView 만들기 (0) | 2023.10.11 |
[Code-Base] CollectionView 안에 CollectionView 넣기 (0) | 2023.10.10 |