FlexLayout

Hyunndy·2023년 6월 24일
0

iOS-FlexLayout

목록 보기
1/2
post-custom-banner

FlexLayout GitHub

FlexLayout이란?

Yoga flexbox (CSS Flexbox 라는 것의 구현체)에 Swift Interface를 추가시킨 Layout Framework

FlexLayout + PinLayout

FlexLayout은 PinLayout와 같이 쓰인다.
PinLayout은 CSS의 absolute Positioning(절대 위치 포지셔닝)에 기반한 layout 프레임워크이다. 더 유연하고 애니메이션에 유용하게 쓰인다.

  • View는 FlexLayout, PinLayout, 아니면 혼합해서 layout 할 수 있다.
  • PinLayout은 어떤 View던 레이아웃 할 수 있지만, 간단하고 애니메이션이 필요치 않으면 FlexLayout이 적합하다.
  • PinLayout을 이용해 레이아웃된 View는 FlexLayout의 Container 안에 배치될 수 있고, 그 반대도 가능하다. 상황에 맞게 잘 선택하면 된다.

FlexLayout 원칙과 철학

  • 간단하고 빠르며, 구문이 간결하고 chainable하다.
  • 수동 레이아웃보다 성능면에서 훨씬 빠르다.

FlexLayout 사용법

Flexbox Container를 만드는 법
1. Container를 만든다.

  • flexBox 구조를 초기화한다.

.
2. Container를 Layout 한다.

  • Container의 Layout은 무조건 다음 함수에서 작성되어야 한다.
  • layoutSubviews()
    - willTransition(to: UITraitCollection, ...)
    - viewWillTransition(to: CGSize, ...)
    • 처음 flexBox Container를 Layout할 때, 반드시 position을 지정하고 크기를 optional로 설정해야 한다.
    • 그 다음 flexBox Container의 Child들을 FlexLayout의 layout() 메서드를 사용해 배치해야한다.

정리
1. Container View를 만든다.
2. Container View.flex.define { ... } 으로 Child의 배치를 넣어준다.
3. layoutSubviews(), willTransition(to:), viewWillTransition(to: ..) 함수에서 Container View를 layout 시킨다. (Pin이던 Flex던 Auto던) layout시킬 때 위치나 크기를 설정한다.
4. FlexLayout의 layout() 메서드를 이용해 FlexBox의 자식들을 배치시킨다.

간단 예시

fileprivate let rootFlexContainer = UIView()

init() {
   super.init(frame: .zero)
   
   addSubview(rootFlexContainer)
   ...

   // Column container
   rootFlexContainer.flex.direction(.column).padding(12).define { (flex) in
        // Row container
        flex.addItem().direction(.row).define { (flex) in
            flex.addItem(imageView).width(100).aspectRatio(of: imageView)
            
            // Column container
            flex.addItem().direction(.column).paddingLeft(12).grow(1).define { (flex) in
                flex.addItem(segmentedControl).marginBottom(12).grow(1)
                flex.addItem(label)
            }
        }
        
        flex.addItem().height(1).marginTop(12).backgroundColor(.lightGray)
        flex.addItem(bottomLabel).marginTop(12)
    }
}

override func layoutSubviews() {
    super.layoutSubviews() 

    // 1) Layout the flex container. This example use PinLayout for that purpose, but it could be done 
    //    also by setting the rootFlexContainer's frame:
    //       rootFlexContainer.frame = CGRect(x: 0, y: 0, 
    //                                        width: frame.width, height: rootFlexContainer.height)
    rootFlexContainer.pin.top().left().width(100%).marginTop(topLayoutGuide)

    // 2) Then let the flexbox container layout itself. Here the container's height will be adjusted automatically.
    rootFlexContainer.flex.layout(mode: .adjustHeight)
}

코드에 쓰인 개념

direction

  • main 축을 결정하는 프로퍼티
  • Column: 세로, row: 가로

addItem(: UIView)

  • flexBox에 ItemView를 추가해준다.
  • 내부적으로 UIView를 생성하고, 그 View를 인자로 전달받은 View의 subView로 넣어서 Flexbox를 활성화 한다.
  • 이렇게 내부적으로 생성&추가 단계를 간소화 시켰기 때문에 chainable 하게 flex.~~~이런식으로 내부에 계속 View를 구성할 수 있는 것.

aspectRatio

  • 주로 이미지, 비디오 및 기타 미디어 유형에 사용되며 일반적으로 이미지나 비디오를 디스플레이 하는 경우 해당 요소의 한 차원(가로 또는 세로)를 알고 있지만, 종횡비(가로:세로 비율)를 유지해야할 때 사용한다.
  • 이미지나 비디오를 표기할 때 원본 비율을 유지하면서 콘텐츠가 왜곡되지 않도록 하는 데 도움을 준다.

grow

  • flex Item이 더 많은 공간을 차지하고자 할 때 사용된다.
  • 주로 flex Container 내의 여분 공간을 활용하여 아이템의 크기를 확장하는데 사용된다.

Margin, Padding 차이

  • Margin: 외부와의 거리
  • Padding: 내부와의 거리

layout(mode: )

  • flexBox안의 Item들을 어떤식으로 layout 시킬지 결정
  • fitContainer
    - 디폴트 값.
    • Children are layouted inside the container's size (width and height).
  • adjustHeight
    - children are layouted using only the container's width. The container's height will be adjusted to fit the flexbox's children
  • adjustWidth
    - In this mode, children are layouted using only the container's height. The container's width will be adjusted to fit the flexbox's children

간단 예시 - 2 (스크롤 뷰)

/*
 스크롤뷰 + 플렉스 뷰
 */
final class Example2View: UIView {
    
    private let scrollView = UIScrollView()
    private let rootFlexContainerView = UIView()
    
    
    
    private let imageView: UIImageView = {
        let view = UIImageView()
        view.image = UIImage(systemName: "star.fill")
        return view
    }()
    private let imageView2: UIImageView = {
        let view = UIImageView()
        view.image = UIImage(systemName: "star.fill")
        return view
    }()
    private let imageView3: UIImageView = {
        let view = UIImageView()
        view.image = UIImage(systemName: "star.fill")
        return view
    }()
    
    private let starImageView: UIImageView = {
        let view = UIImageView()
        view.image = UIImage(systemName: "star.fill")
        return view
    }()
    private let yearlabel: UILabel = {
        let label = UILabel()
        label.text = "2010"
        label.font = .boldSystemFont(ofSize: 50.0)
        return label
    }()
    private let ratinglabel: UILabel = {
        let label = UILabel()
        label.text = "TV-14"
        label.font = .boldSystemFont(ofSize: 50.0)
        return label
    }()
    private let seriesLabellabel: UILabel = {
        let label = UILabel()
        label.text = "3-Series"
        label.font = .boldSystemFont(ofSize: 50.0)
        return label
    }()
    private let label: UILabel = {
        let label = UILabel()
        label.text = "투썸플레이스"
        return label
    }()
    
    init() {
        super.init(frame: .zero)
        
        
        
        // MARK: --
        /**
         backgroundColory
          - 배경
         
         justifyContent
        - flex Container의 현재 줄(main-axis)을 따라 정렬을 정의한다.
         - flex Container의 모든 Child들이 최대 크기에 도달한 경우 남은 여분의 빈 공간을 어떻게 배분할지 결정한다.
            - flex-start (시작점 정렬)
            - flex-end (끝점 정렬)
            - space between (양 끝 정렬 후 중간 균등 정렬)
            - space around (아이템 주위에 균등한 간격)
         
         */
        rootFlexContainerView.flex.direction(.column).define { (flex) in
            
            // 이미지
            flex.addItem(imageView).width(100%).aspectRatio(of: imageView).backgroundColor(.lightGray)
            
            // summary row
            flex.addItem().direction(.row).padding(5.0).define { (flex) in
                flex.addItem(starImageView).width(20%).aspectRatio(of: imageView)
                
                // 년 - 시리즈 - 시리즈
                flex.addItem().direction(.row).justifyContent(.spaceBetween).grow(2.0).define { (flex) in
                    flex.addItem(yearlabel)
                    flex.addItem(ratinglabel)
                    flex.addItem(seriesLabellabel)
                }
                
                flex.addItem().width(100.0).height(1.0)
            }
            
            flex.addItem(imageView2).width(100%).aspectRatio(of: imageView).backgroundColor(.lightGray)
            flex.addItem(imageView3).width(100%).aspectRatio(of: imageView).backgroundColor(.lightGray)
            
        }
        
        scrollView.addSubview(rootFlexContainerView)
        
        addSubview(scrollView)
        
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        // 1. scrollView와 ContainerView를 레이아웃
        scrollView.pin.all(pin.safeArea)
        rootFlexContainerView.pin.top().left().right()
        
        // 2. FlexContainer안의 Child들 배치
        rootFlexContainerView.flex.layout(mode: .adjustHeight)
        
        // 3. adjust the scrollView contentSize
        scrollView.contentSize = rootFlexContainerView.frame.size
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

코드에서 쓰인 개념

backgroundColor

justfyContent

  • StackView처럼 사용하고 싶을 때 유용할 듯
  • flex Container의 현재 줄(main-axis)을 따라 정렬을 정의한다.
  • flex Container의 모든 Child들이 최대 크기에 도달한 경우 남은 여분의 빈 공간을 어떻게 배분할지 결정한다
    - flex-start (시작점 정렬)
    - flex-end (끝점 정렬)
    - space between (양 끝 정렬 후 중간 균등 정렬)
    - space around (아이템 주위에 균등한 간격)

스크롤뷰 + 플렉스 컨테이너

스크롤뷰 안에 플렉스 컨테이너 뷰를 넣을 때에는

    override func layoutSubviews() {
        super.layoutSubviews()
        
        // 1. scrollView와 ContainerView를 레이아웃
        scrollView.pin.all(pin.safeArea)
        rootFlexContainerView.pin.top().left().right()
        
        // 2. FlexContainer안의 Child들 배치
        rootFlexContainerView.flex.layout(mode: .adjustHeight)
        
        // 3. adjust the scrollView contentSize
        scrollView.contentSize = rootFlexContainerView.frame.size
    }

ScrollView의 ContentSize를 정의해주는것이 중요하다.

profile
https://hyunndyblog.tistory.com/163 티스토리에서 이사 중
post-custom-banner

1개의 댓글

comment-user-thumbnail
2024년 5월 24일

Is there anything I need to be careful about when using FlexLayout and PinLayout together? geometry dash subzero asks.

답글 달기