[승용] Building Custom Views with SwiftUI - BoostSwiftUI/SwiftUI GitHub Wiki

레이아웃 과정

  • 부모가 자식 뷰에게 크기를 제안(propose)
  • 자식 뷰가 자기 자신의 크기 결정
  • 부모 뷰가 부모 뷰의 좌표계에 자식 뷰를 위치시킴
  • SwiftUI는 뷰의 코너를 인접한 픽셀로 깎아냄

ex)

struct ContentView: View {
	var body: some View {
		Text("Hello World")
	}
}
  • 부모 뷰(지금 상황에서는 root view)가 Text에게 크기를 제안

    • 부모 뷰가 root 뷰이기 때문에 디바이스 전체 크기를 제안
  • 자식 뷰인 Text는 고맙지만 나는 딱 Hello World를 그려낼 만큼의 크기만 필요하다고 응답

    • SwiftUI에서는 자식 뷰의 크기를 강제시킬 방법이 없다.
  • 부모 뷰(root view)는 자식 뷰를 좌표계 안에 위치시킴

  • 뷰는 자기 자신의 크기를 결정하는 과정이 있음

    • frame, aspectratio 등 명시적인 모디파이어
    • Text등 그 내용을 자기 자신의 크기로 삼는 뷰 등
  • 그리고 SwiftUI는 뷰의 코너를 가장 인접한 픽셀로 깎아내 선명한 모서리를 만들어낸다.

  • 좀 더 복잡한 예시

  • root view가 전체 크기를 background view에게 제안

  • background view는 레이아웃 중립적이기 때문에 해당 크기 제안을 padding view에게 그대로 전달

  • padding 뷰는 child로부터 10 포인트를 추가해야 함을 알기 때문에 그만큼을 뺀 크기를 child에 전달

  • Text 뷰는 필요한 너비를 설정 후 padding 뷰에 반환한다.

  • padding 뷰는 child보다 각 면에서 10포인트 더 커야 한다는 것을 알고, text를 자신의 좌표 공간에 적절하게 배치한다.

  • background는 레이아웃 중립적이기 때문에 padding의 크기를 부모 뷰로 전달

    • 그 전에 Color 뷰에도 그 크기를 제공
    • Color 뷰는 레이아웃에 굉장히 순응적이기 때문에 제공된 크기를 그대로 받아들임 -> 따라서 Color 뷰의 크기는 padding 뷰의 크기와 동일함
  • root 뷰는 background의 크기를 전달받고, 자신의 좌표계 안에 child 뷰(background veiw)를 위치시킴

  • frame은 제약조건이 아니라, 액자의 프레임처럼 그저 뷰일 뿐

  • frame은 자식 뷰에게 크기를 제안하지만, 크기를 결정하는 주체는 자식 뷰이다.

  • 따라서 위와 같이 이미지 뷰에 frame을 적용해도 이미지 크기가 변하지 않는 것은 자연스럽고 의도된 것

  • 이는 SwiftUI는 UIKit보다 가벼운 방식으로 동작하며, 제약조건이 부족하거나 과도할 수 없고 표현할 수 있는 모든 것이 명확한 효과를 가진다.

  • 따라서 결과물이 마음에 들지 않을 수는 있어도, 잘못된 레이아웃이라는 것은 없다.

  • SwiftUI에서 VStack, HStack 등은 spacing을 지정하지 않을 경우 HIG에 맞춰 자동으로 spacing이 주어짐

  • SwiftUI는 앱의 국제화 지원 시, 오른쪽에서 왼쪽으로 읽는 언어를 지원할 땐 자동으로 좌표계를 뒤집음

    • 이것이 left, right 대신 leading, trailing을 사용하는 이유

Stack 알아보기

  • 지금까지 살펴본 뷰들은 자식 뷰들이 선형적으로 연결된 형태였다면, 스택은 자식들이 동일한 조건에서 공간을 놓고 경쟁해야 한다는 점이 다르다.
  • 먼저 스택은 내부적 spacing 요구사항을 살펴보고 스택에게 제안된 크기로부터 그만큼을 제외한 미할당 공간(자식들을 위한)을 만든다.
  • 그리고 해당 공간들을 자식 뷰들의 갯수로 균등하게 나눈 후 나눠진 크기를 자식 뷰 중 가장 덜 유연한 뷰에게 제안한다.
  • Image 뷰가 고정된 크기를 가지기 때문에 여기서는 가장 덜 유연하다.
  • Image 뷰가 확정한 자기자신의 크기만큼 미할당 공간에서 제외하고, 나머지 남은 미할당 공간 크기에서 위 단계를 반복한다.
  • 이렇게 모든 자식 뷰들의 크기가 결정되면 그 후에 지정한 spacing을 사용해 정렬하고
  • 할당된 alignment를 사용해 정렬한다.
  • 마지막으로 스택은 스스로의 크기를 자식 뷰들을 정확히 감싸는 크기로 선택한 후 뷰모 뷰에게 전달한다.

layout priority 알아보기

  • 한정된 스택 공간에서 Delicous / Avocado Toast 등은 linelimit(1)을 가지기 때문에 끝이 truncated된다.
  • 이 때 Delicious가 먼저 크기를 결정하기 때문에 Avocado Toast만 truncated 될 수도 있다.
  • 이를 방지하기 위해서는 layout priority를 올려줄 수 있다.
  • layout priority가 서로 다른 자식 뷰를 스택이 감쌀 때는
  • 먼저 낮은 우선순위의 뷰들의 가장 작은(minimum) 공간을 할당하고
  • 나머지 공간을 가장 높은 우선순위 뷰 부터 할당한다.

alignment 알아보기

  • .lastTextBaseline 사용하면 서로 다른 font의 텍스트 baseline 맞출 수 있음
  • alignmentGuide 사용하면 다른 baseline에 대해 percentage로 지정 가능
  • HStack, VStack에 대해 커스텀 alignment 지정 가능

SwiftUI에서의 drawing

  • SwiftUI는 뷰와 drawing이 사실상 동일한 방식으로 동작 (통합)

    • UIKit에서는 Core Graphics등을 사용해 drawing하는 것이 가능
    • 그러나 SwiftUI는 그러한 drawing은 뷰를 만드는 것과 동일
    • 따라서 Shape과 같은 drawing에도 레이아웃, 애니메이션, 필터 효과 등의 모디파이어들이 똑같이 적용!
  • Shape 프로토콜을 준수하는 커스텀 Shape을 만들 수 있음

  • drawingGroup을 사용하면 모든 SwiftUI 뷰를 하나의 NSViewer UI View로 flatten 시켜서 metal을 사용해 렌더링하도록 할 수 있다.

    • 성능 향상시킬 수 있음