Dex (10)
Widget UI Design
이제 디자인을 해보려고한다
디자인은 이렇게 할 것이다.
UI구성은 지난글에서도 정리했지만 WidgetEntryView
가 담당한다
1
2
3
4
5
6
7
8
9
struct DexWidgetEntryView : View {
var entry: Provider.Entry
var body: some View {
VStack {
entry.sprite
}
}
}
코드를 보면 감이 바로 잡히겠지만, 바로 SwiftUI로 View를 구성한다. (물론 이전글에서 언급을 하긴했다.)
우선 위젯의 사이즈를 여러개 할것이므로
@Environment(\.widgetFamily) var widgetSize
환경 변수를 하나 만들어 준다.
이것 역시 이전에 해봤기때문에 크게 언급은 패스… 이전글 참고
그리고 Preview는 이렇게 더 추가를 해주었다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#Preview(as: .systemSmall) {
DexWidget()
} timeline: {
SimpleEntry.placeholder
SimpleEntry.placeholder2
}
#Preview(as: .systemMedium) {
DexWidget()
} timeline: {
SimpleEntry.placeholder
SimpleEntry.placeholder2
}
#Preview(as: .systemLarge) {
DexWidget()
} timeline: {
SimpleEntry.placeholder
SimpleEntry.placeholder2
}
그럼 상단에 Widget을 선택할수있는게 생기고
이렇게 우리가 설정해둔 사이즈에 따라 어떻게 보이는지 알 수 있다.
Design
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
struct DexWidgetEntryView : View {
@Environment(\.widgetFamily) var widgetSize
var entry: Provider.Entry
var pokemonImage: some View {
entry.sprite
.interpolation(.none)
.resizable()
.scaledToFit()
.shadow(color: .black, radius: 6)
}
var typesView: some View {
ForEach(entry.types, id: \.self) { type in
Text(type.capitalized)
.font(.subheadline)
.fontWeight(.semibold)
.foregroundStyle(.black)
.padding(.horizontal, 13)
.padding(.vertical, 5)
.background(Color(type.capitalized))
.clipShape(.capsule)
.shadow(radius: 3)
}
}
var body: some View {
switch widgetSize {
case .systemMedium:
HStack {
pokemonImage
Spacer()
VStack(alignment: .leading) {
Text(entry.name.capitalized)
.font(.title)
.padding(.vertical, 1)
HStack {
typesView
}
}
.layoutPriority(1)
Spacer()
}
case .systemLarge:
ZStack {
pokemonImage
VStack(alignment: .leading) {
Text(entry.name.capitalized)
.font(.largeTitle)
.lineLimit(1)
.minimumScaleFactor(0.75)
Spacer()
HStack {
Spacer()
typesView
}
}
}
default:
pokemonImage
}
}
}
이건 코드로 대체하는데 크게 언급할 만한게 없다.
한가지 특이점이라면 pokemonImage
가 some View라는 Type이다.
1
2
3
4
5
6
7
var pokemonImage: some View {
entry.sprite
.interpolation(.none)
.resizable()
.scaledToFit()
.shadow(color: .black, radius: 6)
}
해당 변수의 타입을 Image로 하게되면 에러가 발생한다
타입을 Image로 하게되면 Cannot convert return expression of type 'some View' to return type 'Image'
이런 에러가 발생
이유는 .shadow()
같은 modifier는 Image 타입 자체가 아니라, SwiftUI 뷰 조합(some View)에서만 사용할 수 있기 때문이다. 즉, Image는 단순한 이미지 요소이고, .shadow()를 적용한 결과물은 더 이상 Image가 아닌 복합적인 View라는 것이다.
따라서 뷰에 다양한 modifier를 적용할 경우, 반환 타입을 some View로 지정해야 한다.
그리고 실제 적용 될 위젯에서는 modifier쪽만 바꿔준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct DexWidget: Widget {
let kind: String = "DexWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
if #available(iOS 17.0, *) {
DexWidgetEntryView(entry: entry)
.foregroundStyle(.black) // changed
.containerBackground(Color(entry.types[0].capitalized), for: .widget) // changed
} else {
DexWidgetEntryView(entry: entry)
.padding()
.background()
}
}
.configurationDisplayName("Pokemon") // changed
.description("See a random Pokemon.") // changed
}
}
이제 위젯 사이즈에 맞게 유연하게 동작하는 기본적인 포켓몬 위젯 UI가 완성되었다.