안녕하세요. 린다입니다.
요즘 Animation을 공부하면서 WWDC를 잘 활용하고 있는데요.
DragGesture에서 Position, Alignment까지 보고 있습니다..
정말 기초적인 정보가 잘 담겨져 있는 영상인것 같아서
가볍게 정리한 내용인데 블로깅하려고 가져왔어요!
Building Custom Views with SwiftUI - WWDC19 - Videos - Apple Developer
Building Custom Views with SwiftUI - WWDC19 - Videos - Apple Developer
Learn how to build custom views and controls in SwiftUI with advanced composition, layout, graphics, and animation. See a demo of a high...
developer.apple.com
현재 뷰는 3개의 계층을 가지고 있음
- Text, ContentView, RootView
- ContentView의 bounds == body bounds (bounds == 경계) == Text bounds
- RootView (SafeArea를 제외한 Devices 자체의 크기)
- body 뷰가 있는 모든 뷰의 최상위 뷰의 레이아웃은 항상 중립. 그래서 최상위 뷰의 bounds는 body의 bounds를 따른다.
- 그래서 Text == Content 처럼, 동일한 뷰로 처리할 수 있다.
⇒ 실제로 처리하는 뷰는 2개의 뷰 (RootView, Text) 이지만,
처리되는 레이아웃 프로세스는 3개의 뷰 (RootView, ContentView, Text)
SwiftUI의 레이아웃 프로세스
1. Parent Proposes Size for Child
- 부모는 자식에게 사이즈를 제안함
- RootView는 Text에게 → ← 와 같은 사이즈를 제공함 (SafeArea를 제외한 디바이스의 모든 영역)
- Text는 RootView에게 필요한 만큼의 사이즈를 리턴함 (Hello Word의 사이즈만 필요하다고 리턴)
- Text로부터 사이즈를 리턴받은 RootView는 자식뷰(Text)를 자신의 좌표공간내에 배치함
*SwiftUI에서는 자식뷰의 크기를 강요할 수 없으며, 부모는 자식뷰의 사이즈 선택을 존중해야함
2. Child chooses its own Size
- Child chooses its own size == 뷰에 크기 조정 동작이 있음 == 뷰가 자신의 사이즈를 조정할 수 있음
- 해당 프레임은 완전한 고정값
- 50 * 10 RootView로부터 오는 5010의 사이즈 고정값
- 해당 프레임은 유연한 고정값
- 그치만 높이와 너비는 종횡비 설정으로 인해 1:1 비율을 갖게됨⇒ 즉 뷰의 사이즈는 정의에 캡슐화된다 (포함된다)
3. Parent places child in parent’s coordinate space
- 부모뷰는 자식뷰를 좌표 공간에 배치한다
4. SwiftUI rounds coordinates to neartest pixel
- SwiftUI 라운딩 좌표를 가장 가까운 픽셀로 바꾼다.
- 그래서 rounds들은 항상 선명하고 깨끗한 가장자리를 얻을 수 있다. (자동으로)
WWDC내의 아보카도 예시로 확인하기
- background modifier wraps the text, with the color view as a secondary child
- 배경 모디파이어는 텍스트를 감싸고 있으면서 동시에 색상뷰를 자식속성(?)으로 가지고 있음
- green background bounds == Text bounds
- background는 중립이기 때문에 Text의 bounds와 동일치 됨
- +) 사실 swiftui는 동적으로 알아서 적절한 패딩 사이즈를 선택해줌
위의 계층구조를 가지고 Layout 확인하기
- Root View는 Background View에게 → ← 사이즈 전체를 제안함
- Backgrounds 뷰는 중립이므로 해당 크기 제안을 padding 에게 제안함
(저는 이 부분이 이해하기 어려웠는데요.. 1.2.3으로 써볼게요!)
1. padding 뷰는 padding(10)을 통해서 자신의 자식인 Text가 10을 추가할 것이라는 것을 알고있으므로
뷰를 제공할 때 10보다 적은 양의 뷰 사이즈를 제공함
2. padding 뷰는 padding(10)을 통해서 자신의 자식인 Text가 10을 추가할 것이라는 것을 알고있으므로
뷰를 제공할 때 10보다 적은 양의 뷰 사이즈를 제공함
3. Text는 자신이 필요한 사이즈(글자 크기)만큼을 padding View에게 반환함
4. Text로 부터 필요한 사이즈를 반환받은 padding View는 그 사이즈에 미리알고 있던 사이즈 10을 더해서 background에게 반환
5. background는 Text bounds + padding view bounds를 RootView에게 반환하는데,
그러면서 자신의 secondary 계층인 Color에게 bounds를 제공함
=> 그래서 color bounds == (padding bounds includes text bounds)
6. backgrounds로부터 bounds를 반환받은 rootView는 해당 뷰를 자신의 좌표공간에 배치시킴 (default center)
Scrumptious Avocado Example (사진이 있는 예제로 알아보는 layout)
* 현재 아보카도 이미지 사이즈는 20 by 20 으로 고정값
- frame을 30 by 30 으로 변경하여도 사진 자체가 20 by 20 이기 때문에 이미지 자체 크기는 변화는 없음
- image 밖의 frame이 30 by 30 인것 (body of our view)
- image (20 by 20) → frame (30 by 30) == body == content → RootView (== 30 by 30)
HStack and VStack (스택에 뷰를 넣을 때는 layout이 어떻게 될까?)
- Stack이 추가되는 순간, 스택안의 모든 자식 뷰들은 동등한 입장에서 stack 공간을 두고 경쟁해야함
- lineLimit(1) == 텍스트가 한줄이상으로는 줄 바꿈 되면 안됨!
- 즉, 스택이 더 적은 공간에 들어가도록 설정된다면, 텍스트들은 그 공간에 맞춰서 잘림 (Del… Av…)
하지만 공간이 충분하다면?
- Delicious, Image, Avocado 의 3개의 크기를 모르는 자식뷰를 가지고 있을 때,
스택을 3등분하여 그 중 가장 유연성이 작은 자식뷰를 위한 크기로 제안함 - 여기서 유연성이 작은 자식뷰는 20 by 20으로 사이즈가 고정된 이미지뷰
- 즉, Stack - image bounds / 2
- 고정된 이미지의 bounds 만큼을 뺀 bounds에서 사이즈를 모르는 2개의 자식뷰들을 위해 2등분을 함
- 2등분 된 사이즈들을 각 자식뷰가 가지게 됨
그럼 만약에 공간이 부족할때는?
- 이런식으로 잘리게 됨
- 잘리는 순위를 다르게 하고 싶다면 우선순위를 조절하면 됨 (정상이길 바라는 뷰의 우선순위를 높여주기 - default 0)
=> 우선순위가 있는 자식뷰들을 가진 스택뷰일 경우, 스택을 할당되지 않은 공간을 차지하고,
우선 순위가 낮은 모든 자식뷰들을 최소 너비로 설정한 다름, 우선순위가 가장 높은 자식들로 공간을 나눈다.
- Avocado Toast의 우선순위가 가장 높음.
- 그래서 stack 뷰는 …(최소 크기로 축소된 공간)공간 + Image bounds를 제외한 공간을 자식뷰에게 제공
- 이제 가장 높은 우선순위를 가진 모든 자식의 크기를 지정한 후, 스택은 다음으로 가장 높은 레이아웃 우선순위를 가진 자식에게 남은 공간을 나누어줌
Alignment를 알아보자
- 글씨 크기에 따라 정렬이 되기 때문에 .. 정렬이 이상함
- 해결하기 위해서는? 기준선(baseLine)을 기준으로 bottom, top을 설정하면 됨
HStack {
Text("Delicious").font(.caption)
Image("_Avocado_20")
Text("Acovado Toast")
}
HStack(alignment: .lastTextBaseline) {
Text("Delicious").font(.caption)
Image("_Avocado_20")
Text("Acovado Toast")
}
헷갈리신가요?
기준선을 기준으로 동일한 곳에 위치한 것을 확인할 수 있습니다.
근데 여기서...이미지가 조금 올라가있는게 맘에 안 든다면? → 이미지만 따로 조정가능
- 클로저 { d in d[.bottom] * 0.72 }는 정렬 가이드에 대한 실제 계산을 제공함
- 여기서 d는 뷰의 데이터로, .bottom은 뷰의 하단 (HStack)
- 따라서 이 클로저는 뷰의 하단 위치에서 0.972배 만큼의 값을 반환하여 해당 위치를 기준으로 정렬되도록 함
근데.. 사실 또 맘에 안 드는 부분이 있다고 합니다.
바로 별 5개와 아보카도 토스트의 중간이 맞지 않아서 불편하대요.. (WWDC에서..)
그래서 이제는 Custom Alignment를 알아봅시다.
Custom Alignment
=> default alignment = .center이기 때문에 VStack 각각의 센터가 스택 전체의 center으로 정렬이 센터가됨
// 별과 제목의 정렬을 맞추는 방법
extension VerticalAlignment {
private enum MidstarAndTitle: AlignmentID {
static func defaultValue(in context: ViewDimensions) -> CGFloat {
return context[.bottom]
}
}
static let midStarAndTitle = VerticalAlignment(MidstarAndTitle.self)
}
- custom Alignment를 생성하여 별과 타이틀의 중앙을 맞출 수 있음
- 뷰의 바닥을 기준으로 동일한 alignment를 적용
- Custom Alignment → context[.bottom] : 해당 뷰의 y좌표를 반환함
- 뷰의 세로 중앙을 정렬하기 위해서 사용중
.alignmentGuide(.midStarAndTitle, computeValue: { dimension in
dimension[.bottom] / 2
})
- alignmentGuide 는 Custom Alignment를 위해서 사용하는 것.
- 가장 바깥쪽 HStack을 기준으로 Custom Alignment가 함께 적용되는 것.
HStack에 (alignment: .midStarAndTitle) 를 적용하지 않으면, Center와 동일함
(Center 는 뷰의 정 가운데에 배치하는 것 → bottom / 2 에서 바깥 HStack 자체로 범위를 잡아버리기 때문)
오늘은 .. 간단 (?) 하게 스유의 레이아웃 프로세스를 확인해보았어요.
이거 20분짜리인데 해석하고 이해하면서 볼려니까 오래걸리더라구요.
근데 너무 알차서 대만족한 WWDC...
조금 더 생각하면서 뷰를 그려야겠다는 다짐을 하게 된 영상이기도 합니다.
+레이아웃의 다양한 추가예제는 해당 게시물을 확인해주세요!
https://sy-catbutler.tistory.com/68
SwiftUI, Layout 프로세스를 알아보자2 (feat, offset과 position)
안녕하세요. 린다입니다. 저번 WWDC 영상을 보면서 혼자 이것저것 레이아웃 프로세스에 대해서 공부했던? 확인했던..? 예제들을 정리해보려고 합니다. SwiftUI의 레이아웃 프로세스에 관한 글을 안
sy-catbutler.tistory.com
최대한 풀어서 정리해보려고 했는데 틀린게 있다면 말씀주세요!!
이제 또 다른 WWDC와 함께.. 최종은 DragGesture로 돌아오겠습니다. 그럼 오포끝!
'Swift > SwiftUI' 카테고리의 다른 글
SwiftUI, Layout 프로세스를 알아보자3 (feat, Offset와 Position) (0) | 2024.03.28 |
---|---|
SwiftUI, Layout 프로세스를 알아보자2 (feat, offset과 position) (0) | 2024.03.28 |
SwiftUI, Custom TextEditor 만들기 (0) | 2024.03.20 |
SwiftUI, Line Height 명확하게 설정하기 (feat, linespacing) (0) | 2024.03.02 |
SwiftUI, TextField에서 한글과 영어만 입력받는 방법 (특수문자, 초성 제외하기) (0) | 2024.02.22 |