WidgetKit (2)
Dynamic Month 적용
Config
먼저 파일을 만드는데 일반 Swift File로 만든다.
이때 중요한점
target을 어떤것에 적용할지 반드시 확인하자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct MonthConfig {
let backgroundColor: Color
let emojiText: String
let weekdayTextColor: Color
let dayTextColor: Color
static func determineConfig(from date: Date) -> MonthConfig {
let monthInt = Calendar.current.component(.month, from: date)
switch monthInt {
case 1:
return MonthConfig(backgroundColor: .gray,
emojiText: "⛄️",
weekdayTextColor: .black.opacity(0.6),
dayTextColor: .white.opacity(0.8))
case 2:
return MonthConfig(backgroundColor: .palePink,
emojiText: "❤️",
weekdayTextColor: .black.opacity(0.5),
dayTextColor: .pink.opacity(0.8))
//... 후략...
}
}
}
View에 적용
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
struct MonthlyWidgetEntryView : View {
var entry: DayEntry
var config: MonthConfig
init(entry: DayEntry) {
self.entry = entry
self.config = MonthConfig.determineConfig(from: entry.date)
}
var body: some View {
ZStack {
VStack {
HStack {
Text(config.emojiText)
.font(.title)
Text(entry.date.weekDayDisplayFormat)
.font(.title3)
.fontWeight(.bold)
.minimumScaleFactor(0.6)
.foregroundStyle(config.weekdayTextColor)
Spacer()
}
Text(entry.date.dayDisplayFormat)
.font(.system(size: 80, weight: .heavy))
.foregroundStyle(config.dayTextColor)
}
.padding(2)
}
.containerBackground(config.backgroundColor.gradient, for: .widget)
}
}
init을 해주되, 설정값같은 config는 init할때 monthConfig에서 가져오게 했다.
preview에 적용
이전엔 preview역시도 struct로 존재했으나, 지금은 그렇지 않기에
만들어준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct MonthlyWidgetEntryView_Previews: PreviewProvider {
static var previews: some View {
MonthlyWidgetEntryView(entry: DayEntry(date: dateToDisplay(month: 3, day: 22), configuration: .smiley))
.previewContext(WidgetPreviewContext(family: .systemSmall))
}
static func dateToDisplay(month: Int, day: Int) -> Date {
let components = DateComponents(calendar: Calendar.current,
year: 2024,
month: month,
day: day)
return Calendar.current.date(from: components)!
}
}
이러면 자동으로 preview 적용이 된다.
iOS17 적용
강의는 이전에 만들어졌기에 이전글에서 containerBackground에 대한 언급이 없었다.
이부분이 새롭게 추가된 내용이라 코드를 첨부한다.
containerBackground 적용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var body: some View {
ZStack {
VStack {
HStack {
Text(config.emojiText)
.font(.title)
Text(entry.date.weekDayDisplayFormat)
.font(.title3)
.fontWeight(.bold)
.minimumScaleFactor(0.6)
.foregroundStyle(config.weekdayTextColor)
Spacer()
}
Text(entry.date.dayDisplayFormat)
.font(.system(size: 80, weight: .heavy))
.foregroundStyle(config.dayTextColor)
}
.padding(2)
}
.containerBackground(for: .widget){
ContainerRelativeShape()
.fill(config.backgroundColor.gradient)
}
}
이렇게 containerBackground에 담아주었다.
실행화면은 같다.
Standby 적용
그리고 새롭게 standby mode가 나오면서 잠금을 해두었을때도 나타나는데,
이렇게 11월일때는 검은색이라서 안보이게 된다.
이걸 방지하기위해 환경변수를 적용한다.
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
struct MonthlyWidgetEntryView : View {
@Environment(\.showsWidgetContainerBackground) var showsBackground
var entry: DayEntry
var config: MonthConfig
init(entry: DayEntry) {
self.entry = entry
self.config = MonthConfig.determineConfig(from: entry.date)
}
var body: some View {
ZStack {
VStack {
HStack {
Text(config.emojiText)
.font(.title)
Text(entry.date.weekDayDisplayFormat)
.font(.title3)
.fontWeight(.bold)
.minimumScaleFactor(0.6)
.foregroundStyle(showsBackground ? config.weekdayTextColor : .white)
Spacer()
}
Text(entry.date.dayDisplayFormat)
.font(.system(size: 80, weight: .heavy))
.foregroundStyle(showsBackground ? config.dayTextColor : .white)
}
.padding(2)
}
.containerBackground(for: .widget){
ContainerRelativeShape()
.fill(config.backgroundColor.gradient)
}
}
}
showsWidgetContainerBackground의 동작 방식
- true일 때:
- 위젯이 홈 화면이나 잠금 화면 등에서 컨테이너 배경과 함께 표시되는 경우.
- 일반적으로 위젯의 배경이 시스템에 의해 제공되는 영역에 포함될 때.
- false일 때:
- 위젯이 대기 모드(Standby Mode)나 특정 상황에서 컨테이너 배경 없이 표시되는 경우.
- 이 경우 위젯은 투명한 배경 위에 표시되므로, 명시적으로 배경을 추가해줘야 할 수 있다.
또한 Night Mode에서는 다르게 하고싶다면
@Environment(\.widgetRenderingMode) var renderingMode
이걸 추가해준다.
그리고 LockScreen이나, standby등 어떤 조건에서는 위젯을 사용하고 싶지 않다면
1
2
3
4
5
6
7
8
9
10
var body: some WidgetConfiguration {
AppIntentConfiguration(kind: kind, intent: ConfigurationAppIntent.self, provider: Provider()) { entry in
MonthlyWidgetEntryView(entry: entry)
//.containerBackground(.gray.gradient, for: .widget)
}
.configurationDisplayName("Monthly Style Widget")
.description("The theme of the widget changes based on month.")
.supportedFamilies([.systemSmall])
.disfavoredLocations([.homeScreen], for: [.systemSmall])
}
이런식으로 disfavored locations을통해 설정해주면 된다.
그리고 위의 preview역시 이제는 그렇게 지원하지 않기에,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct MockData {
static let dayOne = DayEntry(date: dateToDisplay(month: 9, day: 4), configuration: ConfigurationAppIntent())
static let dayTwo = DayEntry(date: dateToDisplay(month: 10, day: 5), configuration: ConfigurationAppIntent())
static let dayThree = DayEntry(date: dateToDisplay(month: 11, day: 6), configuration: ConfigurationAppIntent())
static let dayFour = DayEntry(date: dateToDisplay(month: 12, day: 7), configuration: ConfigurationAppIntent())
static func dateToDisplay(month: Int, day: Int) -> Date {
let components = DateComponents(calendar: Calendar.current,
year: 2022,
month: month,
day: day)
return Calendar.current.date(from: components)!
}
}
configuration은
여기서 체크를 풀었는데 이걸 체크하면 생기는것이다. (12.04 추가)
해당 프로젝트를 만들때는 아무생각없이 체크를해서 생겨났다.
그러면
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct RepoWatcherWidget: Widget {
let kind: String = "RepoWatcherWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
if #available(iOS 17.0, *) {
RepoWatcherWidgetEntryView(entry: entry)
.containerBackground(.fill.tertiary, for: .widget)
} else {
RepoWatcherWidgetEntryView(entry: entry)
.padding()
.background()
}
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
}
}
이런식으로 초기에 코드가 작성이된다.
1
AppIntentConfiguration(kind: kind, intent: ConfigurationAppIntent.self, provider: Provider())
여기에 intent가 없었다.
그리고 애초에 다른게
1
2
3
StaticConfiguration(kind: kind, provider: Provider()) { entry in
MonthlyWidgetEntryView(entry: entry)
}
Configuration앞에가 다르다. (여기까지가 12.04 수정)
그러면 여러 preview들을 볼수있다.
애니메이션 추가
그리고 숫자의 바뀜을 좀더 역동적으로 하기위해
.contentTransition(.numericText())
이걸 추가
1
2
3
4
Text(entry.date.dayDisplayFormat)
.font(.system(size: 80, weight: .heavy))
.foregroundStyle(showsBackground ? config.dayTextColor : .white)
.contentTransition(.numericText())
1
2
3
4
5
6
7
8
9
10
11
12
13
HStack {
Text(config.emojiText)
.font(.title)
Text(entry.date.weekDayDisplayFormat)
.font(.title3)
.fontWeight(.bold)
.minimumScaleFactor(0.6)
.foregroundStyle(showsBackground ? config.weekdayTextColor : .white)
Spacer()
}
.id(entry.date)
.transition(.push(from: .trailing))
.animation(.bouncy, value: entry.date)
요일쪽도 해보면.
이렆게 된다.
iOS18 적용
새롭게 추가된 기능중 tinted가 있는데
현재는 위젯만 적용이 안되고 있다.
이부분을 해결해보자.
아주 간단하다.
.widgetAccentable()
이걸 추가해주면 된다.
1
2
3
4
5
Text(entry.date.dayDisplayFormat)
.font(.system(size: 80, weight: .heavy))
.foregroundStyle(showsBackground ? config.dayTextColor : .white)
.contentTransition(.numericText())
.widgetAccentable()
이젠 잘되는걸 알수있다.
하지만 하나 문제라면 지금 위에 트리의 색이 사라지고 하얗게 되버린다.
Forum에 관련 이슈를 언급하는 내용이 있어 해결해본다.
1
2
3
.background(Color.black)
.compositingGroup()
.luminanceToAlpha()
이걸 사용해서 해결이 된다고하니 적용해본다.
1
2
3
4
5
Text(config.emojiText)
.font(.title)
.background(Color.black)
.compositingGroup()
.luminanceToAlpha()
이렇게 나오는걸 알 수 있다.
요일과 emoji 모두 색상을 tint에 적용하려면
Hstack에 .widgetAccentable()
만 적용해주면 끝.
그부분은 생략한다.