최근에 스냅킷을 자주 사용하게 되어 정리를 하려고 합니다!
GitHub - SnapKit/SnapKit: A Swift Autolayout DSL for iOS & OS X
A Swift Autolayout DSL for iOS & OS X. Contribute to SnapKit/SnapKit development by creating an account on GitHub.
github.com
podfile에 SnapKit과 Then을 추가하시고 install 해주세요!
스냅킷은 iOS와 OS X에서 오토레이아웃을 쉽게 잡아주는 친구라고 하네요.
직접 사용해 보니까 오토레이아웃을 코드를 사용하여 직관적으로 나타낼 수가 있어서 좋았습니다!
Then은 클로저를 사용해서 인스턴스(여기서는 뷰)를 생성할 때 깔끔하게 작성할 수 있습니다.
SnapKit & Then
일반적으로 라벨을 생성한다! 하면 이런 식으로 정의할 수 있겠죠?
Then을 사용하게 되면 간결하게 작성할 수 있습니다!
클로저니까 같은 코드가 되겠습니다.
SnapKit의 기본적인 사용 방식은
어떤 뷰에 하위 뷰를 넣어주고, 해당 하위 뷰의 레이아웃을 잡는 형태입니다.
뷰에 넣지 않고 레이아웃만 잡아주면 에러가 발생합니다!
으음... 역시 코드를 보는 게 빠를 거 같아요.
직관적으로 코드를 해석할 수 있습니다!
view에 label을 넣어주고,
label의 leading과 trailing, 그리고 Y축 Center를 Superview(여기서는 view겠죠?)에 맞춘다!
실행 후 뷰의 구조를 보면 작성한 대로 잘 보여주고 있습니다.
여기서 중요한 점은 뷰의 레이아웃을 잘 정해줘야 한다는 것! (제약조건, 크기 등)
코드로 작성하기 때문에 빠진 제약조건이 없나 잘 확인을 해야 해요.
그래서 먼저 간략히 제약조건을 생각한 후에 코드를 작성하는 것이 좋아요.
제약조건
간단하게 이러한 뷰를 만든다고 하면! (각 뷰를 redView, blueView, greenView라고 하겠습니다.)
(1) ViewController의 view에 3개의 뷰를 넣어 주고, (아래에서는 편의상 view라고 하겠습니다.)
(2) 3개의 뷰 레이아웃을 잡아준다!
레이아웃을 잡을 때에는 일반적으로 위에서 아래 순으로, 좌에서 우 순으로 잡아주면 편합니다.
그렇게 되면... redView - blueView - greenView 순으로 잡으면 편하겠죠?
[redView]
redView의 좌측(leading), 위(top), 우측(trailing)은 view에 맞추고, 높이만 정해주면 될 거 같습니다.
너비를 따로 잡지 않은 이유는,
leading과 trailing을 view에 맞췄기 때문에 알아서 view 너비에 맞춰지기 때문입니다.
[blueView]
blueView의 leading은 view에 맞추고, top은 redView의 bottom에 맞추면 될 거 같아요!
그러면 이제 뷰의 크기만 정해주면 되겠죠?
여기서는 가로 세로 모두 100으로 설정했습니다.
[greenView]
blueView와 마찬가지로 top은 redView의 bottom에 맞추고, 좌측은 blueView의 우측, 우측은 view에 맞추면 될 거 같습니다.
여기서도 좌측 우측의 레이아웃을 모두 잡아줬기 때문에(blueView의 크기는 이미 정해놨고) 너비는 알아서 잡히게 됩니다.
그렇다면 이제 남은 요소인 높이 값만 정해주면 되겠죠? (마찬가지로 100으로 하겠습니다.)
설명이 길었는데... 이제 코드를 한 번 봅시다.
lazy var redView = UIView().then {
$0.backgroundColor = .red
}
lazy var blueView = UIView().then {
$0.backgroundColor = .blue
}
lazy var greenView = UIView().then {
$0.backgroundColor = .green
}
우선 3개의 뷰를 정의합니다.
private func setupLayout() {
view.addSubview(redView)
view.addSubview(blueView)
view.addSubview(greenView)
redView.snp.makeConstraints {
$0.top.leading.trailing.equalToSuperview()
$0.height.equalTo(100)
}
blueView.snp.makeConstraints {
$0.leading.equalToSuperview()
$0.top.equalTo(redView.snp.bottom)
// 이렇게 사용하면 가로 세로 100, 따로 사용하고 싶다면 CGSize를 넣어주세요
$0.size.equalTo(100)
}
greenView.snp.makeConstraints {
$0.top.equalTo(redView.snp.bottom)
$0.leading.equalTo(blueView.snp.trailing)
$0.trailing.equalToSuperview()
$0.height.equalTo(100)
}
}
이후에 3개의 뷰를 view에 넣어주고 레이아웃을 잡아줍니다.
여기서 좌측에 대해 left가 아닌 leading을 사용했고,
우측에 대해서는 right가 아닌 trailing을 사용했습니다.
우리나라의 경우에는 좌 -> 우로 글을 읽지만, (leading: left, trailing: right)
반대로 읽는 국가도 있기 때문에(leading: right, trailing: left) 이렇게 사용하는 것이 좋습니다!
Stack
이번엔 스택뷰를 사용해 보겠습니다!
lazy var stack = UIStackView().then {
$0.axis = .vertical
$0.spacing = 10
}
일반적으로 이렇게 사용하는데, 방향의 default 값은 horizontal입니다.
spacing은 서브뷰들의 간격!
중요한 것은 스택에 서브뷰를 넣을 때에는 addSubview가 아닌 addArrangedSubview를 사용해야 합니다!
private func setupLayout() {
view.addSubview(stack)
stack.addArrangedSubview(redView)
stack.addArrangedSubview(blueView)
stack.addArrangedSubview(greenView)
stack.snp.makeConstraints {
$0.top.leading.trailing.equalToSuperview()
}
redView.snp.makeConstraints {
$0.height.equalTo(100)
}
blueView.snp.makeConstraints {
$0.height.equalTo(100)
}
greenView.snp.makeConstraints {
$0.height.equalTo(100)
}
}
이렇게 작성하고 실행시켜 보겠습니다.
그러면 이런 결과가 나오는데요,
stack의 제약조건을 먼저 잡아줬기 때문에 (leading과 trailing을 view에 맞춤)
3개의 뷰는 너비를 정하지 않아도 슈퍼뷰에 맞춰졌습니다.
스택뷰의 서브뷰들은
vertical일 경우에는 스택의 너비를 따라가고,
horizontal일 경우에는 스택의 높이를 따라갑니다.
그리고 코드를 보시면 stack의 좌, 우, 상단만 잡아주고 하단에 대한 제약조건은 걸지 않았습니다.
하위 뷰들의 높이를 정해놨기 때문에 높이가 계산되기 때문에 잘 출력될 수 있었던 거죠!
lazy var stack = UIStackView().then {
$0.axis = .vertical
$0.spacing = 10
$0.distribution = .fillEqually
}
스택에 distribution=. fillEqually
private func setupLayout() {
view.addSubview(stack)
stack.addArrangedSubview(redView)
stack.addArrangedSubview(blueView)
stack.addArrangedSubview(greenView)
// edges는 .leading.trailing.top.bottom을 줄여서 사용한 것입니다.
stack.snp.makeConstraints {
$0.edges.equalToSuperview()
}
}
서브 뷰들의 높이를 따로 정하지 않고 스택뷰의 제약조건만 추가하게 되면!
이렇게 각 서브뷰들이 동일한 높이를 가지게 됩니다!
inset & offset
이제 뷰 사이 간격을 설정하는 법을 알아볼게요 (stack뷰의 spacing이 아닌 뷰 사이 간격)
바로 위의 코드에서 stack뷰의 상하좌우 10만큼의 간격을 주고 싶다! (view로부터) 하면...
stack.snp.makeConstraints {
$0.edges.equalToSuperview().inset(10)
}
이렇게 주시면 됩니다.
그럼 이렇게 원하는 결과가 나오게 됩니다.
stack.snp.makeConstraints {
$0.edges.equalToSuperview().offset(10)
}
그런데 이렇게 inset이 아닌 offset을 사용하게 된다면?
????
stack이 우하단으로 치우게 됩니다.
inset은 해당 값만큼 안쪽으로 간격이 생기지만, (값이 음수라면 간격이 바깥쪽으로 생기겠죠?)
offset은 x와 y가 증가하는 방향으로 해당 값만큼 간격이 생기게 됩니다.
계층구조에서는 보이지 않지만,
trailing의 offset 10으로 인해 view보다 오른쪽으로 10만큼의 간격(x가 증가하는 방향)이 생기고,
bottom의 offset 10으로 인해 view보다 아래쪽으로 10만큼의 간격(y가 증가하는 방향)이 생기게 되는 겁니다!
레이아웃 재정의 / 업데이트 / 삭제
지금까지 레이아웃을 추가할 때 makeConstraints를 사용했습니다.
재정의를 할 경우에는 remakeConstraints를 사용하여 정의하면 됩니다.
삭제는 removeConstraints를 사용하면 됩니다.
업데이트를 할 때에는 updateConstraints를 사용하면 되는데...
(1) 업데이트를 하려는 레이아웃의 정의가 되어있어야 하고
(2) 해당하는 레이아웃의 기준은 변경할 수 없습니다.
무슨 소리냐면...
view.addSubview(redView)
view.addSubview(blueView)
redView.snp.makeConstraints {
$0.top.leading.trailing.equalToSuperview()
$0.height.equalTo(100)
}
blueView.snp.makeConstraints {
$0.top.equalTo(redView.snp.bottom)
$0.leading.trailing.equalToSuperview()
$0.height.equalTo(100)
}
이렇게 정의가 되어있다면
redView의 경우
업데이트할 수 있는 부분은 좌, 우, 상단의 offset(혹은 inset)과 height입니다.
redView.snp.updateConstraints {
$0.top.equalToSuperview().inset(30)
$0.leading.equalToSuperview().offset(30)
$0.trailing.equalToSuperview().offset(-30)
$0.height.equalTo(200)
}
이런 식의 업데이트는 가능하지만,
redView.snp.updateConstraints {
$0.top.equalTo(blueView.snp.bottom).inset(30) // 정의해둔 기준과 다릅니다 (view에서 blueView로 변경됨)
$0.center.equalToSuperview // 정의하지 않은 레이아웃입니다.
}
이런 업데이트는 불가능합니다! (remakeConstraints를 사용하세요...)
자주 하는 실수?
(1) 위와 같이 없는 레이아웃을 업데이트하거나 기준을 바꿔서 업데이트하는 경우
(2) view를 추가하지 않고 제약조건만 정의하는 경우 (혹은 추가하지 않은 뷰의 레이아웃에 접근할 경우)
(3) stackView에 addSubview로 서브뷰를 추가하는 경우
정도가 있겠습니다.
'iOS > 기술?' 카테고리의 다른 글
swift - 네이버맵 연동하기 (메소드) (2) | 2023.06.05 |
---|---|
swift - 네이버맵 연동하기 (지도 띄우기) (0) | 2023.05.02 |
swift - ReactorKit (0) | 2022.05.04 |
swift - 카카오맵 API 연동하기 (3) | 2021.11.17 |
swift - CoreLocation을 사용해서 현재 위치(위도, 경도) 가져오기 (0) | 2021.11.15 |