iOS Snapkit 나만의 정리 모음
내가 스냅킷 공부하면서 정리하고자 작성한 자료
추후에 사용하면서 점차 업데이트하기
히스토리
- (init) 2021. 8. 25. 13:21 : 최초 포스팅 (목차 5까지)
- (update) 2022. 1. 14. 21:04 : (목차 6번 추가)
- (update) 2022. 2 .18 20:52 : (목차 7~ 10 추가, LayoutTraining.zip 코드 추가)
- (update) 2022. 9 .22 19:00 : (코드 스타일에 대한 첨언)
- (update) 2022. 9. 25 00:11: (FlexLayout, PinLayout추가) 및 포스팅 코드 스타일 변경
- 예제 파일을 업데이트 하려고 했으나, 너무 해야할 일들이 많아서 포스팅에 나온 코드의 일부를 정리
- (update) 2023. 6. 17 14:04: 블로그에 나온 코드 일부를 컨벤션을 적용한 스타일로 수정
- (update) 2024. 11. 18 02:13: 코드 및 포스팅 스타일 전반적으로 개선
목차
- 1. translatesAutoresizingMaskIntoConstraints = false를 쓰지 않아도 되는 이유
- 2. offset 과 inset의 차이점
- 3. snp.left와 snp.leading의 차이 / snp.right와 snp.trailing의 차이
- 4. Constraint를 updateConstraints로 조정해보기
- 5. Constraint를 remakeConstraints로 조정해보기 (feat. addTarget)
- 6. StackView와 divideby를 활용한 snapKit구성(코드 라인 줄이기?)
- 7. setContentCompressionResistancePriority 알아보기.
- 8. setContentHuggingPriority 알아보기
- 9. SnapKit + animation 효과주기
- 10. remakeConstraints와 updateConstraints 추가 정리
- 11. FlexLayout + PinLayout
1. translatesAutoresizingMaskIntoConstraints = false를 쓰지 않아도 되는 이유
- 일반적으로 코드 기반으로 오토레이아웃을 사용한다면 해당 옵션을 반드시 작성해주어야 함.
- 하지만 SnapKit을 사용한다면 작성하지 않아도 된다.
- 오픈소스 내부에서 prepare() 메소드에서 처리해주고 있음.
2. offset 과 inset의 차이점
Offset
- 정의: 특정 뷰의 경계 외부에서 간격을 설정하는 데 사용
- 설명: offset은 뷰가 기준점(Anchor)에서 떨어져야 하는 거리(간격)를 나타냄. 기준점의 바깥쪽으로 이동
- 예시:
- offset(10): 기준점에서 아래(positive 방향)로 10포인트 떨어짐.
- offset(-10): 기준점에서 위(negative 방향)로 10포인트 떨어짐.
box.snp.makeConstraints {
$0.top.leading.equalToSuperview().offset(50)
$0.bottom.trailing.equalToSuperview().offset(-50)
}
Inset
- 정의: 특정 뷰의 경계 내부에서 간격을 설정하는 데 사용
- 설명: inset은 기준점의 내부에 있는 간격을 나타냄. 주로 뷰의 내부 여백(padding)을 지정할 때 사용
- 예시:
- inset(10): superview의 내부 여백이 10포인트.
- inset(UIEdgeInsets(top: 10, left: 15, bottom: 20, right: 25)): 상, 좌, 하, 우의 여백을 각각 지정.
box.snp.makeConstraints {
$0.edges.inset(50)
}
offset 과 inset 예제 코드의 결과는 같음.
3. snp.left와 snp.leading의 차이 / snp.right와 snp.trailing의 차이
아래의 표처럼 ViewAttribute는 NSLayoutAttribute와 매칭됨
그럼 NSLayoutAttribute의 left와 leading, right와 trailing이 왜 각각 존재하는 이유
- leading, trailing으로 설정하면 right-to-left 순서로 읽는 지역에서 화면이 거꾸로(flip되어서) 표시
- 하지만 left, right로 설정하면 왼쪽 오른쪽 고정.
- 왼쪽, 오른쪽은 모든 지역에서 항상 똑같은 위치이지만, leading, trailing은 각 지역마다 다르게 받아들임.
4. Constraint를 updateConstraints로 조정해보기
스냅킷을 사용하지 않고 오토레이아웃을 처리한다면
- 아래 사진처럼 레이아웃 프로퍼티를 선언하고
- 값을 변경한 후
- isActive를 다시 수행해줌.
updateConstraints를 사용할 경우에는 초기에 makeConstraints에서 잡혀있는 값들만 수정 가능.
- 이건 오토레이아웃도 동일한 원리원칙
- 만약 make에서 없는 제약조건을 변경할 경우 크래시 발생
SnapKit을 활용하면 더 간결하게 처리할 수 있음.
box.snp.makeConstraints {
$0.edges.equalToSuperview().inset(100)
}
button.snp.makeConstraints {
$0.top.equalTo(box.snp.bottom).offset(10)
$0.centerX.equalToSuperview()
}
button.rx.tap
.withUnretained(self)
.bind { owner, event in
owner.box.snp.updateConstraints {
$0.top.left.right.equalToSuperview()
}
}
// MARK: - 위 코드는 RxSwift 6.5 이상을 사용한 예제
// 아래 코드와 의미는 동일
button.rx.tap
.bind { [weak self] event in
self?.box.snp.updateConstraints {
$0.top.left.right.equalToSuperview()
}
}
5. Constraint를 remakeConstraints로 조정해보기 (feat. addTarget)
remake는 말그대로 make 된 값을 지우고 다시 그려내는 과정.
- remake 내부를 보면 뷰 계층에서 remove도 함께 수행함
- remove 때문에 애니메이션 및 다른 UI에 영향을 줄 수 있음.
var button: UIButton = {
$0.backgroundColor = .brown
$0.setTitle("button", for: .normal)
return $0
}(UIButton())
func layout() {
box.snp.makeConstraints {
$0.edges.equalToSuperview().inset(100)
}
button.snp.makeConstraints{
$0.top.equalTo(box.snp.bottom).offset(10)
$0.centerX.equalToSuperview()
}
button.addTarget(self,action: #selector(self.didTappedAction(_:)), for: .touchUpInside)
}
@objc private func didTappedAction(_ sender : UIButton) {
print("didTapButton")
self.box.snp.remakeConstraints {
$0.top.left.right.bottom.equalTo(0)
}
self.button.snp.remakeConstraints {
$0.center.equalToSuperview()
}
UIView.animate(withDuration: 1.0) {
self.view.layoutIfNeeded()
}
}
6. StackView와 divideby를 활용해 SnapKit 구성하기
기본적으로 StackView를 많이 활용하는데, SnapKit을 활용해도 좋음.
import Then
import UIKit
import RxCocoa
import RxSwift
import SnapKit
final class ViewController: UIViewController {
init() {
super.init(nibName: nil, bundle: nil)
configureUI()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
configureUI()
}
//MARK: UIComponents
private let stackViewAuto = UIStackView().then {
$0.distribution = .fillEqually
}
private let connectBtnAuto = UIButton().then {
$0.setTitle("connectBtnAuto", for: .normal)
$0.backgroundColor = .magenta
}
private let writeBtnAuto = UIButton().then {
$0.setTitle("writeBtnAuto", for: .normal)
$0.backgroundColor = .purple
}
private let disconnectBtnAuto = UIButton().then {
$0.setTitle("disconnectBtnAuto", for: .normal)
$0.backgroundColor = .systemPink
}
private let stackViewFirst = UIStackView().then {
$0.distribution = .fillEqually
}
private let connectBtn = UIButton().then {
$0.setTitle("connectBtn", for: .normal)
$0.backgroundColor = .blue
}
private let writeBtn = UIButton().then {
$0.setTitle("writeBtn", for: .normal)
$0.backgroundColor = .green
}
private let disconnectBtn = UIButton().then {
$0.setTitle("disconnectBtn", for: .normal)
$0.backgroundColor = .red
}
private let tableView = UITableView().then {
$0.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
$0.backgroundColor = .brown
}
private func configureUI() {
view.addSubview(stackViewFirst)
stackViewFirst.snp.makeConstraints {
$0.top.left.right.equalToSuperview()
$0.bottom.equalTo(view.snp.centerY).dividedBy(2)
}
view.addSubview(stackViewAuto)
stackViewAuto.snp.makeConstraints {
$0.top.equalTo(stackViewFirst.snp.bottom)
$0.left.right.equalToSuperview()
$0.bottom.equalTo(view.snp.centerY)
}
view.addSubview(tableView)
tableView.snp.makeConstraints {
$0.top.equalTo(view.snp.centerY)
$0.left.bottom.right.equalToSuperview()
}
[connectBtn, writeBtn, disconnectBtn].forEach { stackViewFirst.addArrangedSubview($0) }
[connectBtnAuto, writeBtnAuto, disconnectBtnAuto].forEach { stackViewAuto.addArrangedSubview($0) }
}
}
7. setContentCompressionResistancePriority 알아보기.
setContentCompressionResistancePriority
- 압축에 대한 저항성이 높을수록 다른 것과 비교했을때 내가 줄어들지 않을 수 있음.
title.setContentCompressionResistancePriority(.init(rawValue: 750), for: .horizontal)
title.snp.makeConstraints {
$0.top.leading.equalTo(safeAreaLayoutGuide).inset(16)
$0.trailing.equalTo(author.snp.leading)
$0.width.equalTo(150)
}
author.setContentCompressionResistancePriority(.init(rawValue: 250), for: .horizontal)
author.snp.makeConstraints {
$0.top.trailing.equalTo(safeAreaLayoutGuide).inset(16)
$0.leading.equalTo(title.snp.trailing)
$0.width.equalTo(1000)
}
예제에서는 author width가 1000으로 설정되어 있음.
- author와 title이 서로 레이아웃이 걸려있음.
- author가 width가 1000이라서 전부 차지해야 할 것 같은데, title의 압축저항이 더 높아서 title이 width 150이 보장됨
그렇다면 둘의 우선순위를 바꾸면 어떻게 될까?
title의 압축저항을 낮추면 어떻게 될까?
- 당연하겠지만 author가 모든 width를 차지하게 됨.
- 둘의 레이아웃 충돌 상황에서 title이 저항도가 낮아서 author가 다 가짐
무엇보다 줄어드는 상황에서 적용되는 것이다.
8. setContentHuggingPriority 알아보기
이건 누군가 늘어나야 하는 상황에서의 우선도를 처리함.
- 애는 둘다 100을 주어서 화면을 둘다 전부 차지할 수 없음
- 하지만 leading, trailing을 서로를 잡고 있어서, 빈 영역에 대해서 누군가 늘어나야 하는 상황
- hugging 우선순위가 낮은게 늘어남
- 우선순위가 높으면 "너가 나한테 와서 안아줘"로 해석 가능
title.setContentHuggingPriority(.init(rawValue: 750), for: .horizontal)
title.snp.makeConstraints {
$0.top.equalToSuperView()
$0.leading.equalToSuperView()
$0.trailing.equalTo(bookAuthor.snp.leading)
$0.width.equalTo(100)
}
author.setContentHuggingPriority(.init(rawValue: 250), for: .horizontal)
author.snp.makeConstraints {
$0.top.equalToSuperView()
$0.trailing.equalToSuperView()
$0.leading.equalTo(title.snp.trailing)
$0.width.equalTo(100)
}
9. SnapKit으로 애니메이션 효과처리
스냅킷을 사용해 애니메이션 효과를 처리하고자 함.
- 원하는 레이아웃으로 updateConstraint를 한 후 layoutIfNeed 호출
button.rx.tap
.bind(with: self) { this, controlEvent in
UIView.animate(
withDuration: 1.0
) {
this.layoutIfNeeded()
}
}.disposed(by: bag)
10. remakeConstraints와 updateConstraints 추가 정리
개발하다가 레이아웃 업데이트 하는 지점에서 지속적으로 크래시 만남.
- remakeConstraints -> 새로운 레이아웃을 적용해야 할 경우
- 애는 remove부터 새로 잡음.
- remove하는 것에서 발생하는 사이드 및 레이아웃 렌더링 비용 증가할 수도 있음.
- updateConstraints -> 기존에 작성된 레이아웃에서 값을 변경해야 할 경우
- 얘는 기존에 make로 잡힌 제약이어야 함!
FlexLayout + PinLayout
요즘은 Flex + Pin이 대세인 것 같음.
성능도 더 좋다고 하니 추가적으로 학습해보기
https://rldd.tistory.com/478?category=952720
'apple > iOS, UIKit, Documentation' 카테고리의 다른 글
Swift Protocol (@objc, extension 기본 구현) (0) | 2022.01.31 |
---|---|
iOS Starscream 총정리 (0) | 2022.01.12 |
iOS Snapkit 10 | CollectionView 코드로 구성하는 법 03 (0) | 2021.08.23 |
iOS Snapkit 09 | CollectionView 코드로 구성하는 법 02 (0) | 2021.08.22 |
iOS Snapkit 08 | CollectionView 코드로 구성하는 법 01 (0) | 2021.08.22 |