반응형
https://github.com/ioskrew/SwiftLayout
안녕하세요! 오늘은 SwiftLayout이라는 오픈소스 라이브러리를 소개해보려 합니다.
SwiftLayout은 한마디로 정의하면 UIKit에서 SwiftUI 같은 선언적 레이아웃 관리를 할 수 있도록 도와주는 라이브러리입니다.
if kakao에서 발표된 영상을 간단하게 정리한 게시글이니, 관심이 있으신 분은 구현자분들께서 직접 설명해주시는 아래 영상을 시청해주시면 더 좋을 것 같습니다. (게시글의 모든 코드 역시 해당 영상을 참고하여 작성되었음을 밝힙니다.)
https://if.kakao.com/2022/session/86
SwiftLayout의 필요성
- SwiftUI가 빠른 속도로 발전하고 있지만 아직까지는 한계가 뚜렷하여 프로젝트에 도입하기엔 조금 이릅니다.
- UIKit의 레이아웃 구성방식은 명령형 코드로 한번에 화면 형태를 파악하기 어렵고 분기처리가 있을 경우 코드의 파편화가 심해집니다.
- UIKit에서 SwiftUI같은 스타일의 선언적인 구문을 활용할 수 있다면 매우 좋을 것입니다.
- 그래서 만들어진 것이 SwiftLayout입니다.
Layout 객체
Layout 객체를 활용해 현재 화면의 계층 관계 및 제약사항을 표현합니다.
@LayoutBuilder var layout: some Layout {
view.sublayout {
thumbnailImageView.anchors {
Anchors.height.equalTo(thumbnailImageView, attribute: width)
Anchors.cap(self.view.safeAreaLayoutGuide, offset: 20)
}
contentView.anchors {
Anchors.top.equalTo (thumbnailImageView, attribute: .bottom, constant: 20
Anchors.shoe(self.view. safeAreaLayoutGuide, offset: 20)
}.sublayout {
titleLabel.anchors {
Anchors.top
Anchors.leading
}
descriptionLabel.anchors {
Anchors. top. equalTo (titleLabel, attribute: .bottom, constant: 5)
Anchors.horizontal()
}
menuDetailBottomView. anchors {
Anchors.shoe()
}
}
}
}
// 코드출처 : https://github.com/ioskrew/SwiftLayout-Sample
sublayout
: view의 계층 관계를 표현할 수 있습니다.anchors
: 오토 레이아웃 제약 사항을 부여합니다.- Anchors를 통해 오토 레이아웃 제약 사항들을 표현합니다.
cap
,shoe
: 모자를 씌우거나 신발을 신긴 것처럼 "위,앞쪽,뒤쪽" 혹은 "아래,앞쪽,뒤쪽"에 한번에 제약사항을 적용합니다.- 수치를 제외하고 attribute만 입력할 경우 equalToSuper와 같은 의미가 됩니다.
활성화
SwiftLayout에서는 화면의 계층 관계 정의와 활성화가 구분되어 있습니다. 즉, Layout 객체는 활성화를 해야 적용이 됩니다
finalActive
final class MenuDetailViewController: UIViewController {
...
@LayoutBuilder var layout: some Layout { ... }
override func viewDidLoad() {
super. viewDidLoad()
// Activate Layout
self.layout.finalActive()
}
...
}
// 코드출처 : https://github.com/ioskrew/SwiftLayout-Sample
- Layout 객체에 active 처리를 수행하는 메서드를 호출하는 것으로 활성화가 가능합니다.
- 먼저
finalActive()
는 레이아웃을 활성화하고 추적관리는 하지않는 메서드입니다.
active & Activation
final class MenuDetailViewController: UIViewController {
...
@LayoutBuilder var layout: some Layout { ... }
var activation: Activation?
override func viewDidLoad() {
super. viewDidLoad()
// Activate Layout
self.activation = self.layout.active()
}
func someMethod() {
self.activation = self.layout.update(fromActivation: self.activation)
}
...
}
// 코드출처 : https://github.com/ioskrew/SwiftLayout-Sample
active()
는 레이아웃을 활성화하고 Activation 객체를 반환합니다.- Activation은 뷰의 계층구조와 제약사항을 저장하는 객체입니다.
- 추후 레이아웃이 변경되는 경우 위의 예시처럼 Layout 객체의 update 메서드와 함께 사용이 가능합니다.
deactive
final class MenuDetailViewController: UIViewController {
...
@LayoutBuilder var layout: some Layout { ... }
var activation: Activation?
override func viewDidLoad() {
super. viewDidLoad()
// Activate Layout
self.activation = self.layout.active()
}
func someMethod() {
self.activation?.deactive()
}
...
}
// 코드출처 : https://github.com/ioskrew/SwiftLayout-Sample
- Activation 객체를 활용해 활성화된 레이아웃을 비활성화할수도 있습니다.
- 다만 activation이 메모리에서 해제되면 레이아웃이 자동으로 비활성화되므로
self.activation = nil
과 같은 방식으로 코드를 작성하는 것도 가능합니다. - 이러한 특징이 있는만큼 Activation 객체의 reference는 적절히 관리해줄 필요가 있습니다.
내부 정보 관리
@LayoutBuilder var layout: some Layout {
self.sublayout {
toppingTitleLabel.anchors { ... }
showToppingButton
.config {
$0.setImage(. init (systemName: isShowTopping ? "chevron.down": "chevron.up"), for: normal)
}
.anchors { . .. }
if isShowTopping {
toppingStackView
.anchors { ... }
.sublayout { ... }
}
orderButton.anchors {
if isShowTopping {
Anchors.top.equalTo(toppingStackView, attribute: bottom, constant: 15)
} else {
Anchors.top.equalTo(toppingTitleLabel, attribute: .bottom, constant: 15)
}
...
}
}
}
// 코드출처 : https://github.com/ioskrew/SwiftLayout-Sample
config
: 레이아웃 내부에서 view의 프로퍼티에 접근할 필요가 있을 때 사용합니다.- Layout 객체 내부에서 if, switch-case, for문을 사용할 수 있습니다.
- 위 코드에서는 isShowTopping 값에 따라 화면이 변환됩니다.
final class MenuDetailBottomView: UIView {
...
var activation: Activation?
@LayoutBuilder var layout: some Layout { ... }
var isShowTopping: Bool = false {
didSet {
self.activation = self.layout.update(fromActivation: self.activation!)
}
}
override init (frame: CGRect) {
super.init (frame: frame)
self.activation = self.layout.active()
}
...
}
// 코드출처 : https://github.com/ioskrew/SwiftLayout-Sample
- 물론 didSet 등의 방법으로 관련 프로퍼티 변경 시 Layout 객체의 update 메서드를 수행할 수 있도록 해주어야 합니다.
Layoutable Protocol
updateLayout, @LayoutProperty
final class MenuDetailBottomView: UIView, Layoutable {
...
var activation: Activation?
@LayoutBuilder var layout: some Layout { ... }
@LayoutProperty var isShowTopping: Bool = false
override init (frame: CGRect) {
super.init (frame: frame)
self.sl.updateLayout()
}
...
}
// 코드출처 : https://github.com/ioskrew/SwiftLayout-Sample
- Layoutable 프로토콜은 Activation와 Layout 프로퍼티를 가지도록하는 단순한 구조의 프로토콜입니다.
- 다만 이를 사용하면 update를 더 간결하게 수행할 수 있습니다.
- self.sl.updateLayout()를 수행하면 바로 Activation 객체가 프로퍼티에 저장되고 활성화(업데이트)도 된다고 생각하면 됩니다.
- @LayoutProperty 프로퍼티 래퍼를 사용하면 따로 didSet을 적용하지 않아도 해당 객체가 변경될 때마다 자동으로 레이아웃 업데이트를 수행해줍니다.
@AnimatableLayoutProperty
final class MenuDetailBottomView: UIView, Layoutable {
...
var activation: Activation?
@LayoutBuilder var layout: some Layout { ... }
@AnimatableLayoutProperty(duration: 0.5)
var isShowTopping: Bool = false
override init (frame: CGRect) {
super.init (frame: frame)
self.sl.updateLayout()
}
...
}
// 코드출처 : https://github.com/ioskrew/SwiftLayout-Sample
- @AnimatableLayoutProperty 프로퍼티 래퍼를 사용하면 간단한 애니메이션도 처리할 수 있습니다.
정말 너무 편하지 않나요? 저는 만족도가 너무 높아서 개인 토이프로젝트에서도 사용해볼 계획입니다 👍
긴 글 읽어주셔서 감사합니다ㅎㅎ
반응형
'🍎 Apple > UIKit' 카테고리의 다른 글
[UIKit] UITableView 기초부터 다시 살펴보기 (2) | 2022.04.17 |
---|---|
[UIKit] View Drawing Cycle (0) | 2022.03.16 |
[UIKit] ViewController Life Cycle (0) | 2022.02.12 |
[UIKit] Multiple Gesture Recognizer 처리하기 (0) | 2022.02.05 |
[UIKit] Compositional Layout Example from Apple Sample Code (2) (0) | 2022.01.18 |