UIStackView
UIStackView 는 iOS 개발 과정에서 굉장히 자주 사용되는 Class 이다. 하지만 UIStackView 를 구성할 때 스토리보드 우측 하단의 버튼을 통해서만 구현을 해왔어서, 이번 SnapKit 실습과 함께 UIStackView 와 Subview 에 대하여 공부를 하고자 한다. 이에 앞서 UIStackView 가 무엇인지 알아본다.
출처 : https://developer.apple.com/documentation/uikit/uistackview
공식 문서에 따르면 UIStackView 는 arrangedSubviews 프로퍼티를 통해 모든 뷰의 Layout 을 관리한다.
또한, 각 arrangedSubview 는 arrangedSubviews 리스트의 순서를 기반으로, Stack View 의 axis(vertical / horizontal) 에 따라 정렬된다.
addArrangedSubview(_:) 메서드와 addSubview(_:) 메서드
그렇다면 UIStackView 에 하위 뷰인 arrangedSubview 를 추가하기 위해서는 어떻게 해야할까?
바로 addArrangeSubview 메서드이다.
addArrangedSubView 메서드는 UIStackView 에서 여러 하위 View 들을 추가할 때, 클로저 앞의 배열에 넣은 순서대로 View 가 추가되도록 하는 메서드이다.
[tvButton, movieButton, categoryButton].forEach {
menuStackView.addArrangedSubview($0)
}
하지만 우리는 UIView 에 대해 공부하면서 addSubview 라는 메서드를 공부했었다. UIStackView 에서도 마찬가지로 addSubView 메서드를 통해 Subview 를 추가해도 될까?
"NO"
"왜?"
출처 : https://developer.apple.com/documentation/uikit/uiview/1622616-addsubview
공식 문서에 따르면 addSubview 메서드는 Subview 그 위에 배치된다. 즉, addSubview 를 통해 UIStackView 의 Subview 를 추가한다면, 단순히 Stack View 위에 Subview 를 겹치는 것 밖에 되지 않는다.
아 그럼 UIStackView 를 Content View 에 추가할 때는 사용하면 되겠구낭
하단의 유사 톰과 제리 사진처럼 View 에 Base StackView 가 올라가고, 그 뷰 위에 Menu StackView 가 올라가야한다. 그렇다면 이러한 경우 다음과 같이 addSubview 메서드를 사용할 수 있다.
[baseStackView, menuStackView].forEach {
contentView.addSubview($0)
}
이제 실습을 진행해보자.
SnapKit 과 addArrangedSubView 를 통한 UIStackView 구현 화면
위 화면처럼 톰과 제리 ImageView, Description UILabel 로 구성된 Base StackView 와 3 개의 UIButton 으로 구성된 Menu StackView 를 구현해보자.
구현 과정
여러 컴포넌트와 그에 대한 하위 컴포넌트를 구현하는데에 있어 순서를 헷갈리지 않도록 다음과 같이 구성하였다.
컴포넌트 생성 → 컴포넌트 설정 & 배치 → 배치 코드 이전에 하위 컴포넌트 설정 & 배치 → 하위 컴포넌트를 상위 컴포넌트에 추가
① 컴포넌트 생성
해당 View 는 CollectionView 의 Cell 이므로, UICollectionViewCell Class 를 상속하도록 하였다.
UICollectionViewCell Class 의 메서드인 layoutSubViews() 메서드를 override 하여, 각 Cell 에 포함되는 컴포넌트에 대한 설정을 할 수 있다. 컴포넌트들은 layoutSubViews() 밖에 선언해준다.
class ContentCollectionViewMainCell: UICollectionViewCell {
let baseStackView = UIStackView()
let menuStackView = UIStackView()
// menuStackView
let tvButton = UIButton()
let movieButton = UIButton()
let categoryButton = UIButton()
// baseStackView
let imageView = UIImageView()
let descriptionLabel = UILabel()
override func layoutSubviews() {
}
}
② 상위 컴포넌트인 StackView 설정 & 배치
baseStackView 와 menuStackView 의 설정을 주었다.
// baseStackView
baseStackView.axis = .vertical
baseStackView.alignment = .center
baseStackView.distribution = .fillProportionally
baseStackView.spacing = 5
// menuStackView
menuStackView.axis = .horizontal
menuStackView.alignment = .center
menuStackView.distribution = .equalSpacing
menuStackView.spacing = 20
먼저 baseStackView 에 대한 AutoLayout 을 SnapKit 을 활용하여 부여하였다.
baseStackView.snp.makeConstraints {
$0.lead.top.trailing.bottom.equalToSuperview()
}
SnapKit 에서는 해석 방향이 다음과 같다.
만들다 → 모든 방향을 → Super View 와 동일하게
SnapKit 에서는 다음과 같이 모든 방향을 처리하는 경우에는 edges 로 처리할 수 있다.
baseStackView.snp.makeConstraints {
$0.edges.equalToSuperview()
}
이후 StackView 를 ContentView 에 배치하였다.
[baseStackView, menuStackView].forEach {
contentView.addSubview($0)
}
③ 하위 컴포넌트 설정 & 배치
먼저 Base StackView 의 하위 컴포넌트인 UIImageView 와 Description UILabel 을 설정 및 추가해보자.
// ImageVIew
imageView.contentMode = .scaleAspectFit
imageView.snp.makeConstraints {
// width 도 설정 가능하구나
$0.width.top.leading.trailing.equalToSuperview()
// 너비와 높이가 같도록
$0.height.equalTo(imageView.snp.width)
}
// descriptionLabel
descriptionLabel.font = .systemFont(ofSize: 13)
descriptionLabel.textColor = .white
descriptionLabel.sizeToFit()
SnapKit 에서 width 도 top, leading, trailing, bottom 처럼 부여할 수 있으며, 이미 width 설정을 하였으므로 height 또한 width 에 맞추어 가능하다.
[imageView, descriptionLabel].forEach {
baseStackView.addArrangedSubview($0)
}
다음 Menu StackView 의 하위 컴포넌트인 3가지 버튼에 대해서도 설정과 추가하였다.
[tvButton, movieButton, categoryButton].forEach {
menuStackView.addArrangedSubview($0)
$0.setTitleColor(.white, for: .normal)
$0.layer.shadowColor = UIColor.black.cgColor
$0.layer.shadowOpacity = 1
$0.layer.shadowRadius = 3
}
tvButton.setTitle("TV 프로그램", for: .normal)
movieButton.setTitle("영화", for: .normal)
categoryButton.setTitle("카테고리", for: .normal)
④ 최종 코드
import UIKit
class ContentCollectionViewMainCell: UICollectionViewCell {
let baseStackView = UIStackView()
let menuStackView = UIStackView()
// menuStackView
let tvButton = UIButton()
let movieButton = UIButton()
let categoryButton = UIButton()
// baseStackView
let imageView = UIImageView()
let descriptionLabel = UILabel()
override func layoutSubviews() {
super.layoutSubviews()
[baseStackView, menuStackView].forEach {
contentView.addSubview($0)
}
// BaseStackView 설정
baseStackView.axis = .vertical
baseStackView.alignment = .center
baseStackView.distribution = .fillProportionally
baseStackView.spacing = 5
// Base StackView 에 추가하는 하위 컴포넌트 추가 및 설정
// ImageView 설정
imageView.contentMode = .scaleAspectFit
imageView.snp.makeConstraints {
// width 도 설정 가능하구나
$0.width.top.leading.trailing.equalToSuperview()
// 너비와 높이가 같도록
$0.height.equalTo(imageView.snp.width)
}
// descriptionLabel 설정
descriptionLabel.font = .systemFont(ofSize: 13)
descriptionLabel.textColor = .white
descriptionLabel.sizeToFit()
[imageView, descriptionLabel].forEach {
baseStackView.addArrangedSubview($0)
}
// Base StackView 배치
baseStackView.snp.makeConstraints {
$0.edges.equalToSuperview()
}
// MenuStackView 설정
menuStackView.axis = .horizontal
menuStackView.alignment = .center
menuStackView.distribution = .equalSpacing
menuStackView.spacing = 20
// Menu StackView 에 추가하는 하위 컴포넌트 추가 및 설정
[tvButton, movieButton, categoryButton].forEach {
menuStackView.addArrangedSubview($0)
$0.setTitleColor(.white, for: .normal)
$0.layer.shadowColor = UIColor.black.cgColor
$0.layer.shadowOpacity = 1
$0.layer.shadowRadius = 3
}
tvButton.setTitle("TV 프로그램", for: .normal)
movieButton.setTitle("영화", for: .normal)
categoryButton.setTitle("카테고리", for: .normal)
// Menu StackView 배치
menuStackView.snp.makeConstraints {
$0.top.equalTo(baseStackView.snp.top)
// $0.top.equalTo(baseStackView) 동일
$0.leading.trailing.equalToSuperview().inset(30)
}
}
}
' iOS > UIKit' 카테고리의 다른 글
MVC 구조의 한계와 MVVM (2) | 2022.10.04 |
---|---|
Swift Package Manager 을 통해 SnapKit 설치하기 (0) | 2022.10.01 |
JSON 데이터를 어떻게 사용자 타입으로 변환할 수 있을까? (0) | 2022.09.30 |
NSCache 를 활용한 이미지 캐싱 (1) | 2022.09.30 |
IBOutlet Collection 은 왜 weak 수식어를 붙일 수 없을까? (3) | 2022.09.30 |