포스트

WidgetKit (2)

Dynamic Month 적용

CleanShot 2024-12-03 at 20 51 15

Config

먼저 파일을 만드는데 일반 Swift File로 만든다.

이때 중요한점

CleanShot 2024-12-03 at 20 48 29

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가 나오면서 잠금을 해두었을때도 나타나는데,

CleanShot 2024-12-04 at 13 35 05

이렇게 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의 동작 방식

  1. true일 때:
    • 위젯이 홈 화면이나 잠금 화면 등에서 컨테이너 배경과 함께 표시되는 경우.
    • 일반적으로 위젯의 배경이 시스템에 의해 제공되는 영역에 포함될 때.
  2. 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은

CleanShot 2024-12-04 at 15 13 55 여기서 체크를 풀었는데 이걸 체크하면 생기는것이다. (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들을 볼수있다.

CleanShot 2024-12-04 at 13 50 10

애니메이션 추가

그리고 숫자의 바뀜을 좀더 역동적으로 하기위해

.contentTransition(.numericText())이걸 추가

1
2
3
4
Text(entry.date.dayDisplayFormat)
    .font(.system(size: 80, weight: .heavy))
    .foregroundStyle(showsBackground ? config.dayTextColor : .white)
    .contentTransition(.numericText())

Dec-04-2024 13-51-13

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)

요일쪽도 해보면.

Dec-04-2024 13-55-30

이렆게 된다.

iOS18 적용

새롭게 추가된 기능중 tinted가 있는데

Dec-04-2024 14-00-38

현재는 위젯만 적용이 안되고 있다.

이부분을 해결해보자.

아주 간단하다.

.widgetAccentable()이걸 추가해주면 된다.

1
2
3
4
5
Text(entry.date.dayDisplayFormat)
    .font(.system(size: 80, weight: .heavy))
    .foregroundStyle(showsBackground ? config.dayTextColor : .white)
    .contentTransition(.numericText())
    .widgetAccentable()

Dec-04-2024 14-00-38

이젠 잘되는걸 알수있다.

하지만 하나 문제라면 지금 위에 트리의 색이 사라지고 하얗게 되버린다.

Forum에 관련 이슈를 언급하는 내용이 있어 해결해본다.

1
2
3
   .background(Color.black)
   .compositingGroup()
   .luminanceToAlpha()

이걸 사용해서 해결이 된다고하니 적용해본다.

1
2
3
4
5
Text(config.emojiText)
    .font(.title)
    .background(Color.black)
    .compositingGroup()
    .luminanceToAlpha()

simulator_screenshot_4A0B8824-2BB1-465F-9D8E-79EF5460C5AE

이렇게 나오는걸 알 수 있다.

요일과 emoji 모두 색상을 tint에 적용하려면

Hstack에 .widgetAccentable() 만 적용해주면 끝.

그부분은 생략한다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.