FlexLayout - 2

Hyunndy·2023년 6월 24일
0

iOS-FlexLayout

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

FlexLayout

FlexLayout은 부모 컨테이너인 "Flex Container" 와 하위 요소인 "Flex Items"로 구성된다.

Flex Item 또한 Flex Container가 될 수 있으며, 다른 Flex Item을 추가할 수 있다.

FlexLayout의 핵심 특징은 Item들의 width와 height를 조정하여 공간을 최대한 활용하는 것 이다.

Axes (축)

StackView를 사용할 때 두 가지 축(Axes), 즉 주축과 그에 수직인 교차축에 대해 생각해야 한다.

주축은 StackView의 direction이며, 아이템들이 배열되는 주요 방향을 나타낸다.

이런 축 개념을 사용해 FlexLayout을 설계한다.

(Column: 세로, row: 가로)

Creation, Modification, definition of flexboxItems

addItem(: UIView) -> Flex

Applies To: Flex Container
Return: Flex

Flex Contianer에 새로운 Flex Item을 추가한다.

새롭게 추가된 Flex Item의 FlexLayout Interface를 Return 받는다.

내부적으로 Flex Item에 UIView를 만들어 추가해서 flexBox가 가능하도록 만든다.

  view.flex.addItem(imageView).width(100).aspectRatio(1)

View에 width가 100이고, 종횡비가 1:1로 height 100이 자동으록 계산된 imageView를 추가한다.

define( closure: ( flex: Flex) -> Void) -> Flex

Apples To: Flex Container
Parameter: (flex: Flex) -> Void 클로저 타입
Return: Flex

flexBox 구조를 따르는 코드 구조를 만들기 위해 사용된다.
파라미터인 클로저를 사용해서 View의 Flex Interface를 조작할 수 있다.
Flex Interface를 사용해 다른 Flex Container, Flex Item을 추가할 수 있으며, Flex 구조에 따라 코드를 구성할 수 있다.

 view.flex.addItem().define { (flex) in
      flex.addItem(imageView).grow(1)
		
      flex.addItem().direction(.row).define { (flex) in
          flex.addItem(titleLabel).grow(1)
          flex.addItem(priceLabel)
      }
  }

define을 안써도 되긴하는데, 그럼 코드가 더럽다.

  let columnContainer = UIView()
  columnContainer.flex.addItem(imageView).grow(1)
  view.flex.addItem(columnContainer)
		
  let rowContainer = UIView()
  rowContainer.flex.direction(.row)
  rowContainer.flex.addItem(titleLabel).grow(1)
  rowContainer.flex.addItem(priceLabel)
  columnContainer.flex.addItem(rowContainer)

define 사용의 이점

  • 소스가 flexBox랑 구조가 일치해짐
    flex Item 순서 변경은 그냥 코드에서 줄바꿈만 해도되서 유연함
  • flex Item들을 Container째 이동할 때 편리함
  • define()의 클로저 내부에서는 flex Container를 채우기 위해 for 루프를 사용하거나, 함수 호출 등 다양한 작업 수행 가능

flex Item의 View에 접근

flex.view 프로퍼티를 사용해 flex Item의 View에 접근할 수 있다.

주로 flex.define 메서드의 클로저에서 사용된다.

    view.flex.direction(.row).padding(20).alignItems(.center).define { (flex) in
        flex.addItem().width(50).height(50).define { (flex) in
            flex.view?.alpha = 0.8
        }
    }

아니면 직접 추가 가능.

    view.flex.direction(.row).padding(20).alignItems(.center).define { (flex) in
        let container = UIView()
        container.alpha = 0.8
        
        flex.addItem(container).width(50).height(50)
    }

layout()

Apples To: Flex Container
Values: FitContainer, adjustWidth, adjustHeight
Default: FitContainer

layoutSubViews()에서 Flex Container를 레이아웃 한 다음 Flex Container의 자식들을 layout 시키기 위해 사용된다.

  • FitContainer
    - 자식들이 Container의 크기(너비, 높이) 내에서 레이아웃된다.
  • adJustHeight
    - 자식들이 Container의 너비만 사용하여 레이아웃된다. 컨테이너의 높이는 자식요소들에 맞게 자동으로 조정된다.
  • adjustWidth
    - 자식들이 Container의 높이만 사용해서 레이으웃된다. 컨테이너의 너비는 자식 요소들에 맞게 자도응로 조정된다.

adjustHeight, adjustWidth는 자식들에 의해 Container의 크기가 맞게 조정되고,
FitContainer는 Container의 크기는 유지되고 자식들이 그 안에서 배치되게끔 한다.

 rootFlexContainer.flex.layout(mode: .adjustHeight)

Flexbox Containers Properties

direction()

Apples to: Flex Container
Values: Column(위->아래) / ColumnReverse(아래->위) / row(왼->오) / rowReverse(오->왼)

주 축(main-axis)를 설정하여 이 Container안의 Flex Item들이 배치되는 방향을 정의합니다.

주의할점
row와 row-reverse가 Flex Container의 layoutDirection 속성에 영향을 받는다.

view.flex.direction(.row)
view.flex.direction(.column)

🐸 실험

            
            flex.view?.backgroundColor = .purple
            
            flex.addItem(view1).width(100%).aspectRatio(2.0)
            flex.addItem(view2).width(100%).aspectRatio(2.0)
            flex.addItem(view3).width(100%).aspectRatio(2.0)
        }

Child들을 width(100%), AspectRatio(2.0), JustifyContent를 맥여놨을 때,

layout mode에 따라
FitContainer

  • Pin으로 all 세팅해줬으니까 Child들에 justifyContent, width, aspectRatio 의도대로 동작

AdjustHeight

  • Children들이 Container의 Width만 보고 동작하고, Container의 Height가 Children들에 의해 결정되기 때문에 Children들과 딱!!! 붙는다. 따라서 JustifyContent가 의도대로 동작하지 않는다.

AdjustWidth

  • Children들이 Container의 Height만 보고 동작하는데, 현재 Children들이 Width 100%로 되어있으므로 컨테이너의 width를 몰라서 뷰가 그려지지 않는다. Children이 그려지지 않기 때문에 Container의 너비계산 또한 틀리다.

justifyContent()

Apples to: flex container
Values: start, end, center, spacebetween, spaceAround, spaceEvenly

현재 FlexContainer의 주축(max-axis, direction 속성)에서 주축을 따라 정렬을 정의합니다.

이 속성은 한 줄에 있는 모든 Flex Item들이 최대 크기에 도달했을 때 남은 여분의 공간을 어떻게 분배할지 정하는데 도움이 됩니다.

헷갈리는 spaceAround, spaceEvenly는 이 글에 있는 그림으로 대체 한다.

출처: https://studiomeal.com/archives/197

alignItems()

Applies to: Flex Container
Values: stretch, start, end, center, baseline

Flex Item들이 교차 축(axis)에 따라 어떻게 배치되는지를 정의한다.

justifyContent()은 메인 축, alignItems()는 교차 축을 따라 정렬되는지가 제어된다.

꽉 채우기

stretch 값을 이용해서
세로 축 정렬일 때 가로로 꽉 채우고,
가로 축 정렬일 때 세로로 꽉 채울 수 있다.

       rootFlexContainerView.flex.direction(.column).alignItems(.stretch).define { (flex) in
            
            flex.view?.backgroundColor = .purple
            
            flex.addItem(view1).height(10%)
            flex.addItem(view2).height(10%)
            flex.addItem(view3).height(10%)
        }

stretch 일 때

  • 메인 축이 column이고
  • width를 정의해주지 않았을 때

stretch로 꽉 늘려서 표기해줌

alignSelf()

Applies to: flex Container
value: auto/stretch/start/end/center/baseline

자식 요소가 교차 축에서 어떻게 정렬되는지를 제어하며, 부모 요소의 alignItems를 무시한다.

부모 요소의 alignItems를 무시하고 자식 요소의 개별적인 정렬을 지정할 수 있도록 한다.

노란색 박스 안에 초록, 갈색 박스가 들어있는 형태.

        rootFlexContainerView.flex.direction(.column).alignItems(.stretch).define { (flex) in
            
            flex.view?.backgroundColor = .purple
            
            flex.addItem(view1).direction(.column).height(20%).define { (flex) in
                
                let view = UIView()
                view.backgroundColor = .green
                
                let view2 = UIView()
                view2.backgroundColor = .brown
                
                flex.addItem(view).height(30%)//.aspectRatio(1.0)
                flex.addItem(view2).height(30%)//.aspectRatio(1.0)
            }
            
            flex.addItem(view2).height(20%)
            flex.addItem(view3).height(20%)
        }

여기서 노란색 박스 안의 초록, 갈색만 stretch가 아닌 가로 세로를 갖고 center 정렬 시키고 싶음.

노란색 박스 안의 초록, 갈색에 alignSelf(.center)를 넣어준다.

        rootFlexContainerView.flex.direction(.column).alignItems(.stretch).define { (flex) in
            
            flex.view?.backgroundColor = .purple
            
            flex.addItem(view1).direction(.column).height(20%).define { (flex) in
                
                let view = UIView()
                view.backgroundColor = .green
                
                let view2 = UIView()
                view2.backgroundColor = .brown
                
                flex.addItem(view).alignSelf(.center).height(30%).aspectRatio(1.0)
                flex.addItem(view2).alignSelf(.center).height(30%).aspectRatio(1.0)
            }
            
            flex.addItem(view2).height(20%)
            flex.addItem(view3).height(20%)
        }

wrap()

Applies to: Flex Container
Values: noWrap, wrap, wrapReverse

Wrap 속성은 Flex Item들의 총 넓이가 Flex Container 보다 클 때,
한 줄로 표시되는지 여러 줄로 표시되는지 결정한다.

기본적으로 Flex Container는 모든 Flex Item들을 한 줄에 맞추려 한다.
따라서 들어있는 Flex Item들의 넓이는 Flex Container의 넓이에 맞게 자동 축소된다.

이 속성으로 너비가 더 클 때 어떻게 정렬하는지 정의할 수 있다.

처음에 이해가 안되서 이 글의 그림을 보고 이해했음

alignContent()

Applies to: Flex Container
Values: start, end, center, stretch, spaceBetween, spaceAround

교차축의 justifyContent라고 이해하자.

alignContent 속성은 교차축에서 여분의 공간이 있는 경우 flex Container의 줄들을 컨테이너 내에서 정렬한다.
이는 justifyContent가 main-axis에서 개별 항목들을 정렬하는 방식과 유사하다.

alignContent는 flexBox가 한 줄만 가지고 있는 경우에는 영향을 주지 않는다. 즉, 여러 줄이 있는 경우에만 alignContent 속성이 적용된다.

        rootFlexContainerView.flex.direction(.column).wrap(.wrap).alignContent(.center).define { (flex) in
            
            flex.view?.backgroundColor = .purple
            
            flex.addItem(view1).height(300.0).width(100.0)
            
            flex.addItem(view2).height(300.0).width(100.0)
            flex.addItem(view3).height(300.0).width(100.0)
        }

        rootFlexContainerView.flex.direction(.column).wrap(.wrap).alignContent(.spaceBetween).define { (flex) in
            
            flex.view?.backgroundColor = .purple
            
            flex.addItem(view1).height(300.0).width(100.0)
            
            flex.addItem(view2).height(300.0).width(100.0)
            flex.addItem(view3).height(300.0).width(100.0)
        }

🐸 alignItems와의 차이

둘 다 Flex Container의 교차 축에서 Flex Item들을 정렬하는 데 사용된다.
차이점은
alignItems는 Flex Container의 각 줄에 있는 Flex Item들을 교차축을 따라 정렬하는데 사용한다.
즉, alignItems의 요소에 따라 Flex Item들이 교차 축 방향으로 동일한 위치에 정렬되거나, 시작점이나 끝점에 정렬되거나, 가운데에 정렬된다.

alignContent는 Flex Container의 모든 줄들을 교차축에 따라 정렬하는데 사용된다. 여러 줄이 있는 경우에 사용하며, Flex Container 내에서의 줄들 사이의 간격과 위치를 조정하는데 사용된다.

즉,
alignItems

  • 각 줄 내에서의 Flex Item 정렬
  • 전체 item 배치를 어떻게 할 것 이냐

alignContent

  • 여러 줄 사이의 정렬과 간격을 제어
  • item의 줄 간격을 어떻게 배치할 것 이냐

layoutDirection()

FlexLayout은 LTR, RTL 언어를 지원한다.
start, end속성을 사용하여 뷰를 위치시킬 때, 사용자의 언어에 따라 화면의 왼쪽, 오른쪽에 아이템이 표시되는지 고려할 필요가 없다.


Flexbox Items Properties

flex Container 또한 flex Item이 될 수 있으며, 밑에 기술된 프로퍼티도 flex Container에 적용할 수 있다.

grow()

Applies to: Flex Items
Default: 0

Flex Item이 필요한 경우 확장할 수 있는 능력을 정의한다.
flex Container 내에서 이 Item이 차지해야 하는 사용가능한 공간의 양을 지시한다.

모든 아이템에 grow값을 1로 설정하면 모든 아이템들은 컨테이너 내에서 동일한 크기로 설정된다.
하나의 아이템에 값을 2로 설정한다면, 해당 아이템은 다른 아이템들 보다 2배의 공간을 차지하게 된다.

        rootFlexContainerView.flex.direction(.column).wrap(.wrap).alignItems(.start).alignContent(.spaceBetween).define { (flex) in
            
            flex.view?.backgroundColor = .purple
            flex.addItem(view1).height(300.0).width(100)
            flex.addItem(view2).height(300.0).width(100)
            flex.addItem(view3).height(300.0).width(100)
        }

여기서 노란색 Flex Item에 grow(1)을 적용하면!

노란색 Flex Item이 사용 가능한 공간대로 늘어난다.

shrink()

Apples to: Flex Item
Default: 0

shrink 속성은 main-axis에서 공간이 부족할 때, Flex Container 내의 다른 Flex Item들에 비해 해당 Flex Item이 얼마나 줄어들지를 결정한다.

shrink 값은 음의 공간을 분배할 때 flex 기준 값(flex basis)과 곱해진다.

shrink 값이 0인 경우 뷰의 크기는 main-axis 방향으로 유지되며 이로 인해 뷰가 flex Container를 넘어서는 현상이 발생할 수 있습니다.

이 때 View들에 shrink 속성을 잘 부여해주면..

        rootFlexContainerView.flex.direction(.column).alignItems(.start).define { (flex) in
            
            flex.view?.backgroundColor = .purple
            flex.addItem(view1).height(300.0).width(100).shrink(10.0)
            flex.addItem(view2).height(300.0).width(100).shrink(1.0)
            flex.addItem(view3).height(300.0).width(100).shrink(1.0)
        }

shrink 값은 비율입니다.
한 아이템의 shrink 값이 10이고 다른 아이템들의 shrink 값이 1이라면, 10인 아이템은 나머지 아이템들보다 10배 더 줄어들게 된다.

basis

Applies to: flex Item
Default: 0

flex Item의 초기 크기를 지정한다.
grow, shrink 요소에 따라 여분의 공간이 분배되기 전의 크기다.

Nil을 지정하면 basis가 auto로 설정되며, 만약 Item에 길이가 지정되지 않은 경우, width는 그 Item의 content에 따라 결정된다.

basis( :CGFloat?)
basis(
:FPercent)

isIncludedInlayout()

Applies to: flex Items

특정 UIView를 레이아웃에서 제외하거나 포함할 수 있는 기능을 제공한다.

일반적으로, UIView.flex 속성에 처음 접근하거나 additem() 메서드를 사용하여 자식 View를 flex 컨테이너에 추가할 때 해당 UIView는 자동으로 레이아웃에 포함되는데, 이 속성을 사용해서 동적으로 레이아웃에서 제외시킬 수도 있다.

🐸 레이아웃에서 제외시킨다는건 화면에서 안나온다는건데.. 왜 쓰는건지?

  • 조건부 UI
    • 특정 조건에 따라 UI 요소를 동적으로 표기하거나 숨길 때 사용
    • 화면 전환. 예를 들어 탭 바에서 선택한 탭에 따라 해당 화면의 UI를 구성하는 경우 선택한 탭에 대한 레이아웃만 포함시킬 수 있다.
    • 사용자 설정에 따라 UI 요소의 가시성을 제어할 수 있는 경우
    • 동적으로 변경되는 데이터를 UI에 표기하는 경우 특정 조건을 만족하는 항목만 UI에 표시하고 싶을 때

🐸 UIView.isHidden 쓰면 되는거 아님?

isHidden은 뷰가 숨겨지긴 하지만, 레이아웃에 영향을 주지 않으며, 해당 뷰의 영역은 다른 뷰에 의해 채워지지 않는다.

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Flex container 생성
        flexContainer = UIView()
        view.addSubview(flexContainer)
        
        // Flex item 생성
        flexItem = UIView()
        flexContainer.addSubview(flexItem)
        
        // FlexLayout 설정
        flexContainer.flex.direction(.row).justifyContent(.center).alignItems(.center)
        
        // 데이터를 비동기로 받아오는 작업 수행
        fetchData()
    }
    
    func fetchData() {
        // 데이터를 비동기로 받아오는 예시
        // 네트워크 요청, 데이터베이스 쿼리 등
        
        // 데이터를 받아왔을 때 처리
        // 이 예시에서는 데이터가 있는 경우 flexItem을 표시하고, 없는 경우 flexItem을 레이아웃에서 제외합니다.
        let hasData = true // 데이터가 있는지 여부에 따라서 값을 설정합니다.
        flexItem.isIncludedInLayout = hasData
    }

display

Applies to: flex Items

none으로 두면 hidden되고 layout에 표기되지도 않는다.
그럼 이걸...isIncludedInLayout대신 쓰면 되는거아닌지..?

markDirty()

Applies to: flex Items

flex Item의 레이아웃을 변경하고자 할 때 사용된다.

Flexlayout은 flex 속성(Property)이 변경되거나 flex Container 사이즈가 변경될 때만 레이아웃됩니다.

그러나 특정한 상황에서 FlexLayout에게 특정 Flex Item의 레이아웃을 강제로 다시 계산하도록 하려면, markDirty() 메서드를 사용해 해당 아이템을 '더티' 상태로 표기해야 합니다.

더티 플래그는 flexBox 트리의 루트 까지 전파되어, 하나의 아이템이 invalidate되면 해당 아이템의 전체 하위 트리가 다시 레이아웃되게 업데이트 합니다.

예시.
UILabel의 text가 업데이트 됐을 때 label의 더티 플래그를 켜준다.

    // 1) Update UILabel's text
    label.text = "I love FlexLayout"
     
    // 2) Mark the UILabel as dirty
    label.flex.markDirty()
    
    // 3) Then force a relayout of the flex container. 같이 쓸 필요는 없는데 이 예시에서 이렇게 한 것 같음
    rootFlexContainer.flex.layout()
    OR
    setNeedsLayout()

sizeThatFits()

Applies to: flex Items

지정된 frame size에서 레이아웃된 사이즈를 반환한다.

    let layoutSize = viewA.flex.sizeThatFits(size: CGSize(width: 200, height: CGFloat.greatestFiniteMagnitude))

height는 그냥 큰 값으로 주었을 때 프레임 크기를 추정할 수 있습니다.

🐸 CGFloat.greatesFiniteMagnitude

CGFloat이 표현할 수 있는 가장 큰 유한한 수. 일반적으로 무한대, 아주 큰 값 의미.
주로 레이아웃 계산이나 크기 조정 같은 작업에서 사용된다.
sizeThatFits() 메서드에서 아이템의 크기를 추정할 때, 가능한 크기 범위를 제한하지 않고 아이템이 자유롭게 크기를 조정할 수 있도록 하기 위해 사용될 수 있다.

intrinsicSize

Applies to: Flex Item

뷰 자체의 속성만 고려하여 계산된 아이템의 크기.
아이템의 프레임과는 독립적으로 계산된다.

FlexLayout에서도 View의 intrinsicSize를 사용해서 뷰의 컨텐츠을 적절히 맞추는 레이아웃을 제공할 수 있다.


📌 Flex Container 프로퍼티 정리

direction()
justifyContent()
alignItems()
alignSelf()
wrap()
alignContent()
layoutDirection()

📌 Flex Item 프로퍼티 정리

grow()
shrink()
basis()
isIncludeInLayout()
display()
markDirty()
sizeThatFits()
intrinsicSize

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

0개의 댓글