Views, structures, and propertie
Customize views with properties
Create a SwiftUI app by building custom views to make a multiday weather forecast. In your custom view, you’ll use properties to customize the display for each day.
You design custom views using structures. Structures are a way to organize your code so that related pieces are packaged together. Every structure has a name that lets you reuse it like a template anywhere in your app. With structures, you can write code that is more efficient and has fewer errors.
struct를 사용하여 custom view를 디자인하고 관련 조각들을 패키징하여 재사용 가능하도록 만들기
Tutorial View | My View |
---|---|
날씨를 표시하는 WeatherView가 차지하는 영역을 알기 위해 테두리를 표현하려 했다.
세 가지 방법으로 View의 테두리를 그릴 수 있었고 사용한 modifier은 다음과 같다.
.border()
.background(RoundedRectangle(cornerRadius: 8)
.stroke(lineWidth: 2)
)
.overlay(RoundedRectangle(cornerRadius: 8)
.stroke(lineWidth: 2)
)
세 가지 modifier 모두 비슷한 결과를 얻을 수 있었으며, 특히 .stroke을 사용했을 때 .background와 .overlay의 차이점을 알기 어려워 추가적으로 찾아보니 잘 설명해준 블로그가 있었다.
ZStack vs overlay vs background
날씨에 따라 아이콘 색을 변경하기 위해 다음과 같은 if문을 사용했으나 조금 더 간결한 방법을 떠올리지 못했다.
if weather == "sun.max.fill" {
Image(systemName: weather)
.foregroundStyle(.yellow)
.font(.largeTitle)
.padding(1)
} else if weather == "cloud.rain.fill" {
Image(systemName: weather)
.foregroundStyle(.blue)
.font(.largeTitle)
.padding(1)
} else {
Image(systemName: weather)
.foregroundStyle(.gray)
.font(.largeTitle)
.padding(1)
}
이 부분은 프로퍼티 내부의 if문으로 해결하면 되는 거였는데 후술할 튜토리얼과의 코드 차이점에 적어두겠다.
dot notation을 이용해 "sun.max.fill"을 모두 쓰거나 이런 어려운 아이콘 이름을 알 필요 없이 .sunny로 접근하고 싶었다. 처음엔 dictionary를 사용했으나 원하는 방식으로 접근할 수 없었고 tuple이용해 원하는 대로 할 수 있었다.
Tuple은 ()안에 원하는 값을 나열하는 자료형이다.
Tuple의 값에는 dot notation으로 접근 가능하며 선언 후에는 멤버의 개수와 자료형을 수정할 수 없다.
let weather = (sun: "sunny", cloud: "cloudy)
var foo = (54, "eden", Color(.gray))
var bar = ""
bar = weather.sun //bar의 값은 sunny가 된다.
weather.cloud = "rain" //let으로 선언됐기에 변경 불가능
foo.0 = 23 //이름이 없는 경우 인덱스로 접근할 수 있다.
foo.1 = 42 //foo.1의 자료형은 이미 String으로 고정되어있기에 Int로 바꿀 수 없다.
//초기화 후 자료형을 변경할 수 없기 때문에 첫 초기화까지 자료형을 안 정해놓을 수 있다.
Custom View의 구성이 끝나고 날씨를 나열했을 때, 아이콘의 높이에 따라 View의 높이가 달라진다는 걸 앞서 넣었던 테두리를 통해 알 수 있었다.
.frame이라는 수정자를 통해 사이즈를 고정할 수 있었다.
View 예시를 보고 만든 후 내 코드와 다른 점을 비교해보았다.
앞서 언급한 날씨에 따른 아이콘과 색 변경에 대한 코드가 역시나 달랐다.
튜토리얼은 튜플이 아닌 프로퍼티 내부에서 if문으로 해결했다.
var iconName: String {
if isRainy {
return "cloud.rain.fill"
} else {
return "sun.max.fill"
}
}
var iconColor: Color {
if isRainy {
return Color.blue
} else {
return Color.yellow
}
}
var body: some View {
VStack {
Text(day)
Image(systemName: iconName)
.foregroundStyle(iconColor)
//날씨에 아이콘에 관한 부분이 한 줄로 종료
if문을 프로퍼티 내부로 넣었기에 Image에 대한 modifier을 추가할 수록 if문의 분기마다 Image를 넣은 나와 코드 길이에서 차이가 났다.
if weather == "sun.max.fill" {
Image(systemName: weather)
.foregroundStyle(.yellow)
.font(.largeTitle)
.padding(1)
} else if weather == "cloud.rain.fill" {
Image(systemName: weather)
.foregroundStyle(.blue)
.font(.largeTitle)
.padding(1)
} else {
Image(systemName: weather)
.foregroundStyle(.gray)
.font(.largeTitle)
//색마다 이미지를 넣었기 때문에 길이가 훨씬 길어짐
가장 결정적으로 차이가 나게 된 부분은 .foregroundStyle의 Color를 변수로 넣을 줄 몰랐기 때문이라고 생각한다. Color의 차이를 두기 위해 if문이 길어졌기 때문이다.
튜토리얼에 맞춰 다음과 같이 수정하고 반복되는 코드를 줄였다.
var iconColor: Color {
if weather == "sun.max.fill" {
return Color(.yellow)
} else if weather == "cloud.rain.fill" {
return Color(.blue)
} else {
return Color(.gray)
}
}
Image(systemName: weather)
.foregroundStyle(iconColor)
.font(.largeTitle)
.padding(1)
최종 코드는 다음과 같다.
import SwiftUI
import SwiftData
struct ContentView: View {
let weather = (sunny: "sun.max.fill", rain: "cloud.rain.fill", cloudy: "cloud.fill")
var body: some View {
HStack{
WeatherView(day: "Sat", weather: weather.sunny, highTemperature: 34, lowTemperature: 24)
WeatherView(day: "Sun", weather: weather.rain, highTemperature: 30, lowTemperature: 24)
WeatherView(day: "Mon", weather: weather.cloudy, highTemperature: 31, lowTemperature: 24)
}
}
struct WeatherView: View {
let day: String
let weather: String
let highTemperature: Int
let lowTemperature: Int
var iconColor: Color {
if weather == "sun.max.fill" {
return Color(.yellow)
} else if weather == "cloud.rain.fill" {
return Color(.blue)
} else {
return Color(.gray)
}
}
var body: some View {
VStack{
Text(day)
.font(.title3)
Image(systemName: weather)
.foregroundStyle(iconColor)
.font(.largeTitle)
.padding(1)
Text("High: \(highTemperature)'C")
Text("Low: \(lowTemperature)'C")
}
.frame(minWidth: 80, minHeight: 150)
//.padding()
//.border(.black)
.padding()
.overlay(RoundedRectangle(cornerRadius: 8)
.stroke(lineWidth: 2)
.foregroundStyle(Color.gray)
)
}
}
}
#Preview {
ContentView()
.modelContainer(for: Item.self, inMemory: true)
}