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")
}

img

Section을 사용하여 Form 안에서 요소들을 그룹화 할 수 있다.

Form {
    Section {
        Text("Hello World")
        Text("Hello World")
        Text("Hello World")
    }

    Section {
        Text("Hi!")
    }
}

Section으로 나누는 특별한 규칙은 없고, 연관된 항목끼리 그룹화하기 위해 사용한다.

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로 바뀌면서 포커스가 해제되고, 키보드가 자동으로 닫히게 된다.


카테고리:

업데이트: