[회고] 신입 iOS 개발자가 되기까지 feat. 카카오 자세히보기

🍎 Apple/UIKit

[UIKit] 선언형 레이아웃 라이브러리 SwiftLayout

inu 2022. 12. 14. 20:42

https://github.com/ioskrew/SwiftLayout

 

GitHub - ioskrew/SwiftLayout: A swifty way to use UIKit

A swifty way to use UIKit. Contribute to ioskrew/SwiftLayout development by creating an account on GitHub.

github.com

안녕하세요! 오늘은 SwiftLayout이라는 오픈소스 라이브러리를 소개해보려 합니다.

 

SwiftLayout은 한마디로 정의하면 UIKit에서 SwiftUI 같은 선언적 레이아웃 관리를 할 수 있도록 도와주는 라이브러리입니다.

 

if kakao에서 발표된 영상을 간단하게 정리한 게시글이니, 관심이 있으신 분은 구현자분들께서 직접 설명해주시는 아래 영상을 시청해주시면 더 좋을 것 같습니다. (게시글의 모든 코드 역시 해당 영상을 참고하여 작성되었음을 밝힙니다.)

 

https://if.kakao.com/2022/session/86

 

if(kakao)dev2022

함께 나아가는 더 나은 세상

if.kakao.com


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 프로퍼티 래퍼를 사용하면 간단한 애니메이션도 처리할 수 있습니다.

정말 너무 편하지 않나요? 저는 만족도가 너무 높아서 개인 토이프로젝트에서도 사용해볼 계획입니다 👍

긴 글 읽어주셔서 감사합니다ㅎㅎ