티스토리 뷰

스토리보드와 제약조건 방식의 UI 구성은 관리하기가 매우 어렵습니다.

SwiftUI는 편리하지만 아직 UIKit을 완전히 대체하기엔 무리가 있죠.

그래서 스토리보드 대안으로 사용되는 여러 라이브러리들이 나오게 되었는데요, 그 중 FlexLayout에 대해 알아보겠습니다.

 

 

먼저 FlexLayout과 함께 사용되는 PinLayout부터 알아보겠습니다.

PinLayout


CSS absolute position 방식에 기반한 레이아웃

 

  • 섬세한 조정과 애니메이션에 유용합니다.
  • 한번에 하나의 뷰를 배치할 때 유리합니다.

top, left, bottom, right 속성을 이용해서 레이아웃합니다.

.top(10)이면 super view의 위에서 10 떨어진 곳에 위치하는 방식입니다.

top, left, bottom, right 모두 10을 설정하면 위와 같이 됩니다. 부모뷰에 4면으로 10씩 떨어진 형태로 레이아웃됩니다.

기본적으로 super view 기준으로 레이아웃 되고 .after(of: otherView) , .before(of: otherView) 등의 속성을 통해 형제뷰 간의 레이아웃도 조정할 수 있습니다.

viewC.pin.top().after(of: viewA).before(of: viewB).margin(10) 를 적용한 레이아웃입니다. viewA를 기준으로 뒤에, viewB를 기준으로 앞에 배치하고 top은 super view를 기준으로 했습니다.

 

top, left 등 뿐만 아니라 topCenter, BottomLeft 등의 앵커를 기준으로도 설정 할 수 있으며, safeArea 도 사용할 수 있습니다.

거창하게 말했지만 그냥 AutoLayout Constraint와 같은 방식 입니다!

 

FlexLayout

 


CSS flex 방식에 기반한 레이아웃

 

  • FlexLayout과 PinLayout은 혼용해서 사용할 수 있습니다. (FlexLayout 컨테이너 안에 PinLayout을 사용하거나 FlexLayout 컨테이너에 PinLayout을 적용할 수 있습니다.)
  • 많은 뷰를 배치해야하고 복잡한 애니메이션이 필요하지 않은 경우는 FlexLayout이 PinLayout보다 적합합니다.

FlexContainer 안에 아이템들을 넣고, 정렬 방식을 지정하여 레이아웃하는 방법입니다.

StackView처럼 세로방향, 가로방향으로 레이아웃이 가능합니다.

내부 아이템들은 start, end, center, spaceBetween, spaceAround 등의 키워드를 이용하여 정렬방식을 설정할 수 있습니다.

그 외에도 Wrap 여부, 아이템들이 차지할 영역 비율 등을 설정할 수 있습니다.

 

사실상 CSS Flex와 완전히 똑같아서 이해에 도움이 될만한 블로그를 추천하겠습니다.

CSS Flex의 교과서와도 같은 최고의 정리본입니다! 필요한 속성이 있을 때마다 참고하면 됩니다 👍

https://studiomeal.com/archives/197

 

이번에야말로 CSS Flex를 익혀보자

이 튜토리얼은 “차세대 CSS 레이아웃” 시리즈의 첫번째 포스트입니다. 이번에야말로 CSS Flex를 익혀보자 이번에야말로 CSS Grid를 익혀보자 벌써부터 스크롤의 압박이 느껴지고,‘좀 편안하게 누

studiomeal.com

 

 

이제 실제 프로젝트에 적용해보겠습니다.

설치


저는 SPM을 통해 설치해주었는데요, 공식문서 Installation을 보면 pod를 이용해서도 설치할 수 있습니다.

 

Xcode를 열어 프로젝트를 켜주고 상단에 File - Add pakages를 선택합니다.

그 다음 깃허브 주소를 검색창에 붙여넣고 패키지를 찾으면 오른쪽 하단의 Add Pakage 버튼을 눌러줍니다.

[FlexLayout 깃허브 주소]

https://github.com/layoutBox/FlexLayout.git

 

핀 레이아웃도 같이 사용하기 위해 같은 방법으로 패키지를 추가해줍니다.

[PinLayout 깃허브 주소]

https://github.com/layoutBox/PinLayout.git

 

⛔️ SPM ERROR


SPM으로 설치시 꼭 해주셔야하는 작업이 있습니다.
만약 Could not build Objective-C module 'FlexLayoutYogaKit' 이라는 오류와 YGEnums.h' file not found라는 오류가 떴다면 다음 방식으로 해결할 수 있습니다.

 

빌드 오류 메세지

 

TARGET → Build Settings → Preprocessing Macros 에 'FLEXLAYOUT_SWIFT_PACKAGE=1' 이라는 변수를 추가해주어야 합니다. 

Debug 옆에 + 버튼 누르고 생긴 칸에 FLEXLAYOUT_SWIFT_PACKAGE=1 을 넣어주면 됩니다.

저는 Debug, Release 둘다 설정해주었습니다.

 

🔽 상세한 에러 원인 

더보기

해당 에러를 따라가보니

이 부분에서 오류가 나는 것을 확인했습니다.

패키지 내부 파일에서 에러가 난건데요, 코드를 잘 읽어보니 Swift 패키지이면 yoga 폴더 안에 헤더 파일을, 아니라면 그냥 헤더 파일을 import하는 코드였습니다.

보아하니 Swift 패키지임을 인식 못해서 else문으로 넘어가며 문제가 생긴 것 같습니다.

그래서 SPM Installation을 다시 읽어보니 SPM을 사용하려면 ‘FLEXLAYOUT_SWIFT_PAKAGE’ 라는 매크로 변수를 따로 설정해주어야 한다는 내용이 있었습니다.

Docs를 꼼꼼히 읽어야겠습니다...

 

 

FlexLayout 사용법


본격적으로 코드를 작성해보겠습니다.

대략 이런걸 만들어보겠습니다.

 

 

1. flex container 세팅

먼저 새로운 Swift파일을 하나 만든 후 UIView를 상속하는 클래스를 하나 만들어주고 안에 FlexContainer가 될 UIView(rootFlexContainer)를 만들어줍니다.

import UIKit
import FlexLayout
import PinLayout

class MyFlexView: UIView{
	// root뷰가 될 컨테이너 선언
	fileprivate let rootFlexContainer = UIView()
}

 

2. container에 Item 추가

import UIKit
import FlexLayout
import PinLayout

class MyFlexView: UIView{
    // root뷰가 될 컨테이너 선언
    fileprivate let rootFlexContainer = UIView()
    
    // 컨테이너 안에 들어갈 아이템들
    let plusButton = UIButton(configuration: .filled())
    let subtractButton = UIButton(configuration: .filled())
    let count = UILabel()
    let footer = UILabel()

    init() {
        
        // init 내부에 아이템들 생성
        super.init(frame: .zero)
        backgroundColor = .white
        
        // Button 설정
        plusButton.setTitle("+", for: .normal)
        subtractButton.setTitle("-", for: .normal)

        // Label 설정
        count.textAlignment = .center
        count.font = UIFont.systemFont(ofSize: 60.0)
        
        // footerLabel 설정
        footer.text = "https://dokit.tistory.com/"
        
        
        // 생성한 아이템들을 배치
        rootFlexContainer.flex.width(100%).direction(.column).padding(12).define { (flex) in
            
            // 첫 번째 행 - 버튼
            flex.addItem().direction(.row).justifyContent(.spaceBetween).define { (flex) in
                flex.addItem(plusButton).grow(1).marginRight(10)
                flex.addItem(subtractButton).grow(1)
            }
            
            // 두번째 행 - 라벨
            flex.addItem().direction(.row).justifyContent(.center).define { (flex) in
                flex.addItem().direction(.column).paddingLeft(12).define { (flex) in
                    flex.addItem(count)
                }
            }
            
            // 마지막 - 푸터 라벨
            flex.addItem().height(1).marginTop(12).backgroundColor(.lightGray)
            flex.addItem(footer).marginTop(12)
        }
        
        addSubview(rootFlexContainer)
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
    
        // 부모뷰에 대한 컨테이너 레이아웃 설정 (SafeArea에 맞춰서)
        rootFlexContainer.pin.all(pin.safeArea)
        // 컨테이너 사이즈 설정 (내부 아이템 높이에 맞춰서)
        rootFlexContainer.flex.layout(mode: .adjustHeight)
    }
    
}

컨테이너 안에 들어갈 요소들(버튼, 라벨 등)을 클래스 변수로 생성해주고 .flex메서드를 통해 아이템을 배치해줍니다.

적용하고싶은 레이아웃이 있다면 체이닝을 통해 설정할 수 있습니다.

 

💡 required init?(coder aDecoder: NSCoder)

해당 메서드는 UIView를 상속했다면 구현해주어야 하는 메서드입니다.
NSCoder는 스토리보드나 xib를 뷰로 빌드하기 위한 객체입니다.
그대로 써주시면 됩니다! (안쓰면 에러가 뜹니다)

💡override func layoutSubviews()
뷰의 크기가 변경될 때마다 하위 뷰들의 layout을 조정해주는 메서드입니다.
이 메서드에서 rootFlexContainer 자체의 레이아웃을 잡아주어야 합니다.

 

3. storyboard(+ViewController)와 연결

import UIKit

class ViewController: UIViewController {
    
    // FlexLayout을 적용한 뷰 인스턴스 생성
    fileprivate let myFlexView = MyFlexView()
    
    // 내부적으로 카운트할 변수
    var count: Int = 0

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }
    
    override func loadView() {
        // FlexLayout 뷰 지정
        view = myFlexView
        
        // 뷰 초기화 및 버튼 액션 연결
        myFlexView.count.text = "\(count)"
        myFlexView.plusButton.addTarget(self, action: #selector(plusCount(_:)), for: .touchUpInside)
        myFlexView.subtractButton.addTarget(self, action: #selector(subtractCount(_:)), for: .touchUpInside)
    }
    
    // MARK: - Button Action
    @objc
    func plusCount(_: UIButton!) {
        count += 1
        mainView.count.text = "\(count)"
    }
    
    @objc
    func subtractCount(_: UIButton!) {
        count -= 1
        mainView.count.text = "\(count)"
    }
}

스토리보드를 생성하고 ViewController를 연결해준 뒤 func loadView() 메서드를 오버라이드 해줍니다.

그리고 내부에 view(==self.view)를 아까 만든 클래스(MyFlexView)의 인스턴스로 바꿔줍니다.

💡 func loadView() 메서드는 ViewController가 자신의 뷰를 생성할 때 호출되는 함수이며 내부에서 view에 원하는 UIView를 대입해줄 시 이 뷰를 로드해줍니다.

 

완성입니다! 이제 빌드해보시면 됩니다 ㅎㅎ

 

 

마치며


제가 스토리보드로 작업한 View를 코드로 조작하기 위해서 작성한 코드였습니다.

일단 translatesAutoresizingMaskIntoConstraints를 통해 제약조건을 깨는 순간부터 모든 제약조건을 다시 설정해주어야하며,

그 코드 길이도 결코 짧지 않아 가독성이 매우 떨어집니다...

여기에 뷰를 여러개 설정해야한다면 코드가 정말 길어지겠죠.

이걸 해결하기 위해 SnapKit등 많은 라이브러리가 있는데요, 저는 CSS Flex에 익숙해서 이 라이브러리가 기대가 됩니다!

하지만 실제 프로젝트에 적용하다보면 여러 문제점들을 마주칠 수 있을 것 같습니다.

좀 더 써보고 난 뒤 추후에 추가 포스팅하겠습니다.

 

읽어주셔서 감사합니다.

 

 

 

Ref.

https://github.com/layoutBox/FlexLayout (FlexLayout 공식문서)

https://github.com/layoutBox/PinLayout (PinLayout 공식문서)

https://ios-development.tistory.com/195 ([iOS - swift] layout subviews 메서드)

https://github.com/layoutBox/FlexLayout/blob/master/Example/FlexLayoutSample/UI/Examples/Intro/IntroView.swift (FlexLayout 예제)

https://jeonyeohun.tistory.com/359 (NSCoder와 객체 그래프)

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함