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

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

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

반응형