Upgrade to Pro — share decks privately, control downloads, hide ads and more …

SwiftLayout을 소개합니다

kakao
PRO
December 08, 2022

SwiftLayout을 소개합니다

#iOS #OpenSource

SwiftLayout은 UIKit을 사용하면서 아쉬웠던 부분들을 개선하기 위해 선언적인 구문으로 UIKit을 사용할 수 있도록 도와주는 오픈소스 라이브러리입니다.
SwiftLayout의 사용법을 코드와 함께 살펴보며 저희가 라이브러리를 개발하는 과정에서 격은 여러 의미 있었던 경험도 함께 공유하고자 합니다.

발표자 : aiden.h
저는 카카오톡을 개발하고 있는 iOS 개발자 aiden 입니다.

jay.choi
저는 카카오톡을 개발하고 있는 iOS 개발자 jay 입니다.

kakao
PRO

December 08, 2022
Tweet

More Decks by kakao

Other Decks in Programming

Transcript

  1. 선언형 문법으로 가독성 높은 UIKit 코드 작성하기
    SwiftLayout을 소개합니다.
    이희종 aiden.h, 최광훈 jay.choi


    카카오
    if(kakao)2022
    Copyright 2022. Kakao Corp. All rights reserved. Redistribution or public display is not permitted without written permission from Kakao.

    View Slide

  2. UIKit SwiftUI
    아직까진 약방의 감초? 아직은 시기상조?

    View Slide

  3. - Interface Builder


    - 유지보수가 어렵다.


    - Con
    fl
    ilct 발생시 해결이 쉽지 않다.


    - Code


    - 명령형 코드로 화면의 형태를 파악하기 어렵다.


    - 분기처리로 코드의 파편화가 심해질 수 있다.
    UIKit

    View Slide

  4. 선언적인 구문으로 UIKit을?

    View Slide

  5. SwiftLayout
    A swifty way to use UIKit

    View Slide

  6. View Slide

  7. View
    Thumbnail
    ContentView
    Title
    Description
    BottomView

    View Slide

  8. @LayoutBuilder var layout: some Layout {


    view.sublayout {


    thumbnailImageView


    contentView.sublayout {


    titleLabel


    descriptionLabel


    menuDetailBottomView


    }


    }


    }
    View
    Thumbnail
    ContentView
    Title
    Description
    BottomView

    View Slide

  9. @LayoutBuilder var layout: some Layout {


    view.sublayout {


    thumbnailImageView


    contentView.sublayout {


    titleLabel


    descriptionLabel


    menuDetailBottomView


    }


    }


    }

    View Slide

  10. @LayoutBuilder var layout: some Layout {


    view.sublayout {


    thumbnailImageView.anchors { ... }


    contentView.sublayout {


    titleLabel


    descriptionLabel


    menuDetailBottomView


    }


    }


    }

    View Slide

  11. @LayoutBuilder var layout: some Layout {


    view.sublayout {


    thumbnailImageView.anchors {


    Anchors.top.equalToSuper()


    }


    contentView.sublayout {


    titleLabel


    descriptionLabel


    menuDetailBottomView


    }


    }


    }

    View Slide

  12. @LayoutBuilder var layout: some Layout {


    view.sublayout {


    thumbnailImageView.anchors {


    Anchors.top.equalToSuper(constant: 5)


    }


    contentView.sublayout {


    titleLabel


    descriptionLabel


    menuDetailBottomView


    }


    }


    }

    View Slide

  13. @LayoutBuilder var layout: some Layout {


    view.sublayout {


    thumbnailImageView.anchors {


    Anchors.top.equalTo(view)


    }


    contentView.sublayout {


    titleLabel


    descriptionLabel


    menuDetailBottomView


    }


    }


    }

    View Slide

  14. @LayoutBuilder var layout: some Layout {


    view.sublayout {


    thumbnailImageView.anchors {


    Anchors.top.equalTo(view.safeAreaLayoutGuide)


    }


    contentView.sublayout {


    titleLabel


    descriptionLabel


    menuDetailBottomView


    }


    }


    }

    View Slide

  15. @LayoutBuilder var layout: some Layout {


    view.sublayout {


    thumbnailImageView.anchors {


    Anchors.bottom.equalTo(contentView, attribute: .top, constant: -20)


    }


    contentView.sublayout {


    titleLabel


    descriptionLabel


    menuDetailBottomView


    }


    }


    }

    View Slide

  16. @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()


    }


    }


    }


    }

    View Slide

  17. Activate Layout

    View Slide

  18. final class MenuDetailViewController: UIViewController {


    ...


    @LayoutBuilder var layout: some Layout { ... }


    override func viewDidLoad() {


    super.viewDidLoad()


    // Activate Layout


    }


    ...


    }

    View Slide

  19. final class MenuDetailViewController: UIViewController {


    ...


    @LayoutBuilder var layout: some Layout { ... }


    override func viewDidLoad() {


    super.viewDidLoad()


    // Activate Layout


    self.layout.finalActive()


    }


    ...


    }

    View Slide

  20. final class MenuDetailViewController: UIViewController {


    ...


    @LayoutBuilder var layout: some Layout { ... }


    var activation: Activation?


    override func viewDidLoad() {


    super.viewDidLoad()


    // Activate Layout


    self.activation = self.layout.active()


    }


    ...


    }

    View Slide

  21. final class MenuDetailViewController: UIViewController {


    ...


    @LayoutBuilder var layout: some Layout { ... }


    var activation: Activation?


    override func viewDidLoad() {


    super.viewDidLoad()


    // Activate Layout


    self.activation = self.layout.active()


    }


    ...


    }

    View Slide

  22. 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)


    }


    ...


    }

    View Slide

  23. 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()


    }


    ...


    }

    View Slide

  24. 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 = nil


    }


    ...


    }

    View Slide

  25. sublayout(_
    :)

    anchors(_
    :)

    Activate Layout

    View Slide

  26. View Slide

  27. @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)


    }


    ...


    }


    }


    }

    View Slide

  28. final class MenuDetailBottomView: UIView {


    ...




    var activation: Activation?


    @LayoutBuilder var layout: some Layout { ... }


    var isShowTopping: Bool = false


    override init(frame: CGRect) {


    super.init(frame: frame)


    self.activation = self.layout.active()


    }


    ...


    }

    View Slide

  29. final class MenuDetailBottomView: UIView {


    ...




    var activation: Activation?


    @LayoutBuilder var layout: some Layout { ... }


    var isShowTopping: Bool = false


    override init(frame: CGRect) {


    super.init(frame: frame)


    self.activation = self.layout.active()


    }


    ...


    }

    View Slide

  30. 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()


    }


    ...


    }

    View Slide

  31. final class MenuDetailBottomView: UIView, Layoutable {


    ...


    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()


    }


    ...


    }

    View Slide

  32. final class MenuDetailBottomView: UIView, Layoutable {


    ...


    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()


    }


    ...


    }

    View Slide

  33. final class MenuDetailBottomView: UIView, Layoutable {


    ...


    var activation: Activation?


    @LayoutBuilder var layout: some Layout { ... }


    var isShowTopping: Bool = false {


    didSet {


    self.sl.updateLayout()


    }


    }


    override init(frame: CGRect) {


    super.init(frame: frame)


    self.sl.updateLayout()


    }


    ...


    }

    View Slide

  34. 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()


    }


    ...


    }

    View Slide

  35. 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()


    }


    ...


    }

    View Slide

  36. Control
    fl
    ow


    Layoutable


    LayoutProperty

    View Slide

  37. SwiftLayout
    A swifty way to use UIKit

    View Slide

  38. 냄새 없는 코드
    낮은 진입장벽
    간결한 문법
    라이브러리의 방향성

    View Slide

  39. Pull Request
    Testing and Refactoring

    View Slide

  40. - github


    https:/
    /github.com/ioskrew/SwiftLayout


    - sample
    -
    source


    https:/
    /github.com/ioskrew/SwiftLayout
    -
    Sample
    SwiftLayout

    View Slide