[WWDC] Compose custom layouts with SwiftUI

Judy·2023년 5월 24일
0

WWDC

목록 보기
8/19
post-thumbnail

SwiftUI의 Layout

  • Text, Image 및 Graphic을 이용해 커스텀 Layout도 만들 수 있음
  • HStack, VStack과 같은 컨테이너는 상대적인 뷰를 배치할 위치를 알려줌
  • modifire는 간격 및 정렬 같은 추가 제어를 제공

1. Grid

정적인 뷰 세트를 2차원 레이아웃으로 나타내기

순위 차트

모델

  • Pet 모델 정의
  • ForEach에서 쉽게 사용할 수 있도록 Identifier 준수
  • 애니메이션 변경사항을 저장할 수 있도록 Equatable 준수

Lazy Grid vs Grid

Lazy Grid

  • 스크롤 가능한 콘텐츠에 적합
  • 표시되거나 표시될 뷰만 로드하므로 뷰가 많을 때 매우 효율적
  • 반면) 컨테이너가 두 차원 모두에서 셀의 크기를 자동으로 조절할 수 없음
  • LazyHGrid의 경우 column을 그리기 전 모든 뷰를 측정하여 column을 얼마나 넓게 만들지 파악할 수 있지만 row의 높이를 파악할 수 없음 -> 초기화 시 해당 차원에 대한 정보를 제공해야 함

Grid

  • 스크롤이 필요 없음
  • 모든 뷰를 한 번에 로드하여 열과 행 모두 셀의 크기를 자동으로 조정하고 정렬 가능

차트 구현

1. 진행률 보기가 가능한 많은 공간을 확보하기

  • 글자는 가질 수 있는 최소한의 공간만 갖도록 하기
  • GridGridRow로 구성
    모델 타입을 ForEach를 이용해 간단하게 표현할 수 있음

2. 이름은 leading, 투표 수는 trailing에 정렬

  • Grid의 alignment는 leading으로
  • 특정 column 정렬하기 -> gridColumnAlignment

3. Divider로 구분선 나누기

  • Divider는 flexible 뷰라 공간이 같지 않음
    = 하나의 뷰만(이름까지만) 구분해줌
  • gridCellColumns 수정자로 단일 뷰가 몇 개의 열에 걸쳐 있는지 알려줌

투표 버튼

  • 같은 크기를 가지도록 하기
  • 화면에 차도록 너무 커져도 안됨. 가장 넓은 버튼 텍스트와 같은 크기

HStack

HStack으로 하면 버튼이 text에 맞게 크기가 조정됨
-> 텍스트의 max width를 infinity로 하면 늘어날 수 있음
-> 각 버튼의 크기는 같지만 컨테이너 만큼 커짐 😔

버튼의 이상적인 크기를 요청하고 가장 넓은 것을 찾아 버튼에 제공하는 custom stack이 없나? -> Layout

Layout

레이아웃 엔진과 상호 작용할 수 있는 사용자 지정 뷰 컨테이너 타입을 만들 수 있는 프로토콜

텍스트 넓이 만큼을 최대 넓이로 갖도록 하는 사용자 지정 컨테이너 만들기

Layout 프로토콜을 준수

두 가지 required 메서드 구현

1) sizeTahtFits

레이아웃 컨테이너의 크기를 계산하고 반환

2) placeSubViews

레이아웃의 하위 뷰가 표시될 위치를 알려줌

Geometry Reader를 사용할 경우 문제

Geometry Reader는 뷰 크기를 측정하기 위한 도구

  • 컨테이너 뷰를 측정하고 해당 크기를 하위 뷰에 보고하도록 설계
  • 하위 뷰는 해당 정보를 사용하여 자체 콘텐츠를 그림

Container -> Geometry reader -> Subview
-> 정보가 아래로 흐름

  • Geometry Reader의 측정은 자체 컨테이너의 레이아웃에 영향을 주지 않음
  • 수행할 수는 있지만 레이아웃 엔진을 우회하게 되어 루프가 발생할 수 있음
  • 레이아웃 프로토콜은 레이아웃 엔진 내에서 작업할 수 있도록 함

Geometry Reader 사용

  • 컨테이너와 함께 크기가 조정되는 path 같은 작업에 유용
  • 작업하는 공간을 경로에 알려주고 그에 따라 하위 뷰 내부의 경로 논리가 조정됨
  • 컨테이너 크기가 변경되면 새 크기를 전달하여 경로도 변경됨

아바타 표시하기

순위 별로 이미지 높이를 다르게 회전하자 -> 역시 Layout 프로토콜을 이용하면 가능

  • replacingUnspecifiedDimensions
    = 컨테이너가 이상적인 크기를 요구하는 경우 존재할 수 있는 경우 자동으로 nil 값을 처리

순위를 어떻게 받아와서 회전을 계산하지?
레이아웃은 뷰가 아닌 하위 뷰 프록시에만 접근 가능

=> 레이아웃 프로토콜은 하위 뷰에 값을 저장하고 프로토콜 메서드 내부에서 값을 읽을 수 있다!

Layout 내부에서 데이터 읽기

  1. LayoutValueKey 프로토콜을 준수하는 타입 선언 및 기본값 지정
  2. View를 extension하여 메서드를 생성해 layourValue 수정자를 통해 값 설정
  3. 뷰 계층 구조에서 레이아웃 뷰에 해당 메서드를 수정자로 적용 가능

Dynamic

이상적인 크기를 갖도록 하고 너비를 제한하지 않아 디스플레이 너비를 초과하는 문제가 발생할 수 있음 🙁
=> ViewThatFits

ViewThatFits

뷰 컬렉션에서 사용 가능한 공간에 맞는 뷰를 자동으로 선택하는 컨테이너

ViewThatFits로 래핑하면 버튼을 다르게 배열해야 할 때 SwiftUI가 파악할 수 있음

ViewThatFits {
	MyEqualWidthHStack {
		Buttons(pets: $pets)
	}

	MyEqualWidthVStack {
		Buttons(pets: $pets)
	}
}

여러 컨테이너를 넣어 상황에 맞는 컨테이너를 선택하도록 함

AnyLayout

레이아웃 타입 간에 원활한 전환 추가

모두 동점인 경우는 회전 Layout으로 표시 불가 -> 모두 동점일 땐 HStack을 이용하도록 변환
=> AnyLayout

AnyLayout을 사용하면 단일 뷰 계층 구조에 다른 레이아웃을 적용할 수 있음
-> 한 레이아웃에서 다른 레이아웃으로 전환 가능

let layout = isThreeWayTie ? AnyLayout(HStack()) : AnyLayout(MyRadialLayout())
    
layout {
	ForEach(pets) { pet in
		Avator(pet: pet)
			.rank(rank(pet))
	}
}

AnyLayout에 교체할 레이아웃을 넣어 조건에 따라 변경

결론

  1. Grid: 정적 정보의 고도로 사용자 정의 가능한 2차원 레이아웃
  2. Layout: 고유한, 재사용 가능한 레이아웃 또는 특정 사례를 레이앗을 정의할 수 있음
  3. ViewThatFits: 가능한 뷰 공간에서 가장 잘 맞도록
  4. AnyLayout: 레이아웃 타입 간의 원활하게 전환 가능




WWDC - Compose custom layouts with SwiftUI

profile
iOS Developer

0개의 댓글