SwiftUI body에 사용할 수 없는 것과 대안들 : 변수선언, 조건문, 반복문
오늘은 SwiftUI body에 사용할 수 없는 것들에 대해 정리하겠습니다.
1. SwiftUI body에 사용할 수 없는 것들
SwiftUI는 명령형이 아닌,
선언형 프로그래밍 방식을 따르는데요.
SwiftUI View의 body에서는,
UI의 상태와 구조만을 '선언'하도록 하고 있습니다.
그래서, body안에 사용할 수 없는 것들이 있는데요.
이에 대해 명확히 알아 둘 필요가 있습니다.
SwiftUI body에 사용할 수 없는 것들은 다음과 같습니다.
- 변수 선언 (var 키워드)
- 명령형 조건문 (if, switch 등을 값 할당에 사용할 때)
- 명령형 반복문 (for, while 등)
예를 들어,
아래와 같이 코드를 작성하면,
컴파일 에러가 발생합니다.
struct ContentView: View {
var body: some View {
VStack {
// 오류: View의 body 내에서 직접 변수 선언 불가
var message = "안녕하세요"
// 오류: View의 body 내에서 직접 조건문 사용 불가
if true {
message = "반갑습니다"
}
Text(message)
}
}
}
Swift의 for-in 루프도 사용불가능합니다.
struct ContentView: View {
let items = ["항목 1", "항목 2", "항목 3"]
var body: some View {
VStack {
// 오류: 명령형 for 루프 사용 불가
for item in items {
Text(item)
}
}
}
}
그럼 SwiftUI사용하기 너무 어려워 지는 것 아닐까요?
대안들이 있는데요.
지금부터는 SwiftUI body에 사용할 수 있는 것들을 알아보겠습니다.
2. let 키워드로 지역 상수 사용하기
body 내에서 let 키워드를 사용하여,
아래와 같이 지역 상수를 선언할 수도 있습니다.
옵셔널에 대한 언래핑이나,
비교를 통한 계산된 값 등을,
let 키워드를 이용한 지역상수에 저장해 사용할 수 있습니다.
struct BusDetailView: View {
let bus: BusModel
var body: some View {
VStack {
// 옵셔널 언래핑과 기본값 설정
let routeType = Int(bus.routeType) ?? NullDataConstants.NULL_ROUTE_TYPE
// 계산된 값 저장
let isExpress = routeType == BusTypes.EXPRESS
// 배열에서 인덱스로 값 가져오기
let statusText = ["운행중", "차고지 대기", "운행 종료"][bus.status]
// 여러 조건을 결합한 표현식
let statusColor = isExpress && bus.status == 0 ? Color.green : Color.gray
Text(busNumber)
.foregroundColor(statusColor)
}
}
}
3. 삼항 연산자 사용
body내에서 아래와 같이 삼항 연산자를 사용할 수 있고요.
var body: some View {
VStack {
let busNumber = bus.busNumber.isEmpty ? "알 수 없음" : bus.busNumber
...
}
}
아래와 같이 사용할수도 있습니다.
struct ContentView: View {
var shouldShowGreeting = true
var body: some View {
VStack {
Text(shouldShowGreeting ? "안녕하세요 반갑습니다" : "안녕하세요")
}
}
}
3. ForEach
위에서 Swift의 for in문을 사용할 수 없다는 것을 보았는데요.
그럼 어떤 대안이 있을까요?
바로 ForEach인데요.
ForEach는 그 자체로 View이므로 다른 View 내에 포함될 수 있습니다.
아래와 같이 사용해 주면 됩니다.
struct ContentView: View {
let items = ["항목 1", "항목 2", "항목 3"]
var body: some View {
VStack {
ForEach(items, id: \.self) { item in
Text(item)
}
}
}
}
좀 더 복잡한 경우를 보면 다음과 같습니다.
struct TodoItem: Identifiable {
let id = UUID()
let title: String
var isCompleted: Bool
}
struct TodoListView: View {
let todos = [
TodoItem(title: "우유 사기", isCompleted: false),
TodoItem(title: "이메일 확인하기", isCompleted: true),
TodoItem(title: "운동하기", isCompleted: false)
]
var body: some View {
List {
// Identifiable 프로토콜을 준수하는 객체는 id 파라미터 생략 가능
ForEach(todos) { todo in
HStack {
Image(systemName: todo.isCompleted ? "checkmark.circle.fill" : "circle")
Text(todo.title)
.strikethrough(todo.isCompleted)
}
}
}
}
}
4. if-else 구문을 View 선택에 사용하기
View를 리턴해 준다면,
if-else문도 사용할 수 있습니다.
struct ContentView: View {
var shouldShowGreeting = true
var body: some View {
VStack {
// if-else를 사용해 다른 View 반환
if shouldShowGreeting {
Text("안녕하세요 반갑습니다")
} else {
Text("안녕하세요")
}
}
}
}
5. Switch문 사용하기
view를 리턴해주기만 한다면,
switch문도 사용할 수 있습니다.
struct ContentView: View {
enum WeatherType {
case sunny, rainy, cloudy, snowy
}
var currentWeather: WeatherType = .sunny
var body: some View {
VStack {
switch currentWeather {
case .sunny:
Text("맑은 날씨")
.foregroundColor(.yellow)
.background(Image("sun"))
case .rainy:
Text("비")
.foregroundColor(.blue)
.background(Image("rain"))
case .cloudy:
Text("구름")
.foregroundColor(.gray)
.background(Image("cloud"))
case .snowy:
Text("눈")
.foregroundColor(.white)
.background(Image("snow"))
}
}
}
}
viewBuilder를 사용하여 아래와 같이 분리할수도 있습니다.
struct ContentView: View {
enum WeatherType {
case sunny, rainy, cloudy, snowy
}
var currentWeather: WeatherType = .sunny
var body: some View {
VStack {
weatherView
Text("오늘의 날씨 정보였습니다")
}
}
@ViewBuilder
private var weatherView: some View {
switch currentWeather {
case .sunny:
Text("맑은 날씨").foregroundColor(.yellow)
case .rainy:
Text("비").foregroundColor(.blue)
case .cloudy:
Text("구름").foregroundColor(.gray)
case .snowy:
Text("눈").foregroundColor(.white)
}
}
}
6. body에서 함수 사용하기
SwiftUI의 body에서 한가지 더 할 수 있는 것이 있는데요.
함수를 선언하거나 사용하는 것 입니다.
6-1. 함수 호출하기
아래와 같이,
body외부에 정의한 함수를,
사용할 수 있습니다.
struct ContentView: View {
@State private var counter = 0
var body: some View {
VStack {
Text("카운터: \(counter)")
Button("증가") {
incrementCounter()
}
}
}
// View 내에 함수 정의
func incrementCounter() {
counter += 1
}
}
하나 더 볼까요?
아래에서는 이름을 합쳐서 보여주는 함수와,
나이를 계산하는 함수를 외부에 정의해 사용하고 있습니다.
struct ProfileView: View {
let user: User
var body: some View {
VStack {
Text(formatName())
Text("나이: \(calculateAge())")
}
}
func formatName() -> String {
return "\(user.lastName) \(user.firstName)"
}
func calculateAge() -> Int {
// 생년월일로부터 나이 계산 로직
let calendar = Calendar.current
let ageComponents = calendar.dateComponents([.year], from: user.birthDate, to: Date())
return ageComponents.year ?? 0
}
}
View외부에서 정의한 함수도 호출할 수 있습니다.
func formatCurrency(_ value: Double) -> String {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = Locale.current
return formatter.string(from: NSNumber(value: value)) ?? "0"
}
struct PriceView: View {
let price: Double
var body: some View {
Text("가격: \(formatCurrency(price))") // 외부 함수 호출
}
}
3-2. 조건부 뷰 생성을 위한 함수 사용하기
조건에 따라서 View를 생성해야하는 경우,
if-else문을 사용하는 함수를 ViewBuilder로 만들어,
사용할 수 있습니다.
struct WeatherView: View {
let temperature: Double
var body: some View {
VStack {
Text("\(Int(temperature))°C")
getWeatherIcon() // 함수 호출하여 뷰 생성
}
}
// 조건에 따라 다른 View를 반환하는 함수
@ViewBuilder func getWeatherIcon() -> some View {
if temperature > 30 {
Image(systemName: "sun.max.fill")
.foregroundColor(.orange)
} else if temperature > 20 {
Image(systemName: "sun.min.fill")
.foregroundColor(.yellow)
} else if temperature > 10 {
Image(systemName: "cloud.sun.fill")
.foregroundColor(.gray)
} else {
Image(systemName: "cloud.fill")
.foregroundColor(.gray)
}
}
}
이상으로 SwiftUI body에 사용할 수 없는 것들과,
그 대안들을 살펴보았습니다.