100 Days of SwiftUI - Day 16
Day 16에는 SwiftUI의 전반적인 구조와 @State
, Form
, Picker
등에 대해 알아보고 사용하는 방법을 익힌다.
SwiftUI 어플의 기본 구조
WeSplit이라는 어플 생성 시 몇 개의 기본 파일이 생성된다.
- WeSplitApp.swift
- ContentView.swift
- Assets.xcassets
- Preview Contents (디렉토리)
Form
사용자 입력과 관련된 요소들을 모아서 관리할 수 있는 컨테이너 TextField, Toggle, Picker, Text 등의 요소가 포함될 수 있다.
Form 안에는 원하는 만큼 요소를 넣을 수 있다.
Form {
Text("Hello World")
Text("Hello World")
Text("Hello World")
Text("Hello World")
Text("Hello World")
Text("Hello World")
Text("Hello World")
}
Section
을 사용하여 Form 안에서 요소들을 그룹화 할 수 있다.
Form {
Section {
Text("Hello World")
Text("Hello World")
Text("Hello World")
}
Section {
Text("Hi!")
}
}
Section으로 나누는 특별한 규칙은 없고, 연관된 항목끼리 그룹화하기 위해 사용한다.
Navigation Bar
Swift의 뷰들은 개발자의 의도에 따라 화면 어느곳에나 배치될 수 있다. 하지만 보기 좋지 않기 때문에 기본적으로는 모서리에 가려지지 않는 부분인 안전 영역 (Safe Area)에 뷰가 배치된다.
iPhone 14 Pro 시리즈, 15 시리즈의 경우는 다이나믹 아일랜드 밑부터 홈 표시 바 위쪽까지가 안전영역이다.
하지만 Form과 같은 스크롤 가능한 뷰들로 인해 안전영역 바깥에 표시되어 콘텐츠가 가려질 수 있다.
이 문제를 해결하기 위한 방법 중 하나는 내비게이션 바를 사용하는 것이다. 내비게이션 바에는 화면 상단에 제목과 버튼들을 배치할 수 있으며 새로운 뷰로 이동할 수 있는 기능도 제공한다.
NavigationStack
을 뷰의 최상위에 위치하고 감싼다.
var body:some View {
NavigationStack {
Form {
...
}
.navigationTitle("SwiftUI")
}
}
Form에 추가된 수정자 .navigationTitle()
은 내비게이션 바에 제목을 추가한다.
내비게이션 바에 제목을 추가하고 싶은 경우, NavigationStack
안에 배치된 뷰에 수정자 .navigationTitle()
을 사용한다.
기본적으로는 제목이 크게 표시되는데, 제목의 텍스트 크기를 조절하고 싶은 경우 .navigationBarTitleDisplayMode()
수정자를 추가한다.
- .navigationBarTitleDisplayMode()의 옵션
- .automatic
- .inline
- .large
@State
간단한 프로퍼티의 상태를 저장하는 경우, 프로퍼티 앞에 @State라는 프로퍼티 래퍼를 붙인다.
프로퍼티 래퍼는 프로퍼티 앞에 붙일 수 있는 특수한 속성이다.
@State는 하나의 뷰에서 하나의 값을 저장하기 위해 설계된 프로퍼티 래퍼이다.
Apple에서는 @State 프로퍼티에 private 접근 제어를 사용하도록 권장한다.
ex) @State private var tapCount = 0
view는 state의 함수
UI의 표시는 어플의 상태에 따라 달라진다.
swift는 var body를 mutating으로 선언할 수 없게 막는다. 그 대신, 프로퍼티 래퍼 (property wrapper)를 사용하여 구조체 안에서 프로퍼티의 값을 자유롭게 수정할 수 있다. 여러 가지의 프로퍼티 래퍼가 있고, 그 중 하나인 @State에 대한 설명.
@State 값이 변할 수 있는 부분을 SwiftUI에서 따로 분리해서 저장. 값이 변화할때마다 변경 내용을 갱신함 하나의 뷰에서 프로퍼티 값을 저장하는 데 사용. —
팁 퍼센테이지를 선택할 Picker 추가
tipPercentages 배열에 미리 정의해둔 지불할 팁의 퍼센테이지를 선택하기 위해 Picker를 사용한다.
선택할 수 있는 요소가 적은 경우 Picker를 세그먼트 스타일로 지정하면 훨씬 보기 좋다.
Section의 매개변수로 문자열을 전달하여 머리글 또는 바닥글을 추가할 수 있다. 여기서는 얼마를 팁으로 남길 것인지 안내하는 머리글을 추가하였다.
Section("How much do you leave?") {
Picker("Tip Percentage", selection: $tipPercentage) {
ForEach(tipPercentages, id: \.self) {
Text($0, format: .percent)
}
}
.pickerStyle(.segmented)
}
인당 분할 금액 계산
계산된 프로퍼티를 이용하여 인당 지불해야 할 금액을 계산한다.
인당 지불 금액 = 총 금액 + (총 금액 * 팁 퍼센테이지) / 인원 수
Double
형의 tipPerPerson
계산된 프로퍼티를 사용하여 인당 지불할 금액을 계산한다.
인원 수를 선택하는 경우 인덱스와 실제 값이 2만큼 떨어져있기 때문에, 2를 더해 위치를 보정한다.
var totalPerPerson: Double {
//선택한 인원수와 팁의 퍼센테이지
let peopleCount = (numberOfPeople+2)
let tipSelection = Double(tipPercentage)
let tipValue = checkAmount / 100 * tipSlection
let grandTotal = checkAmount + tipValue
let amountPerPerson = grandTotal / peopleCount
return amountPerPerson
}
이후 세번째 Section에 작성되어 있는 Text의 총 금액 checkAmount
변수를 인당 지불 금액 amountPerPerson으로
변경한다.
어플이 입력값을 통해 인당 지불할 금액을 잘 계산하는 것을 볼 수 있다.
@FocusState
가상 키보드를 사용하는 경우, TextField에 값을 입력한 후 키보드가 닫히지 않는 현상이 일어난다.
숫자 키보드에는 리턴 키가 없어, 키보드를 해제할 수 없다.
@FocusState
는 TextField 등의 포커스(커서 깜빡임)을 처리하기 위해 설계된 프로퍼티 래퍼이다.
포커스 여부를 확인하기 위해 Bool형으로 프로퍼티를 정의한 후, TextField에 .focused() 수정자를 추가한다.
@FocusState private var amountIsFocused: Bool
...
TextField {
...
}
.focused($amountIsFocused)
TextField에 포커스가 있을 때 툴바에 버튼을 표시해, 버튼을 누르면 키보드가 닫히는 방식으로 동작하도록 한다.
Form의 .navigationTitle() 아래에 코드를 추가한다.
.toolbar {
if amountIsFocused {
Button("Done") {
amountIsFocused = false
}
}
}
TextField에 포커스가 있을 때 값 입력 후 Done버튼을 누르면, amountIsFocused가 false로 바뀌면서 포커스가 해제되고, 키보드가 자동으로 닫히게 된다.