https://simth999wrld.tistory.com/77
이제 조금은 기능들을 거의 다 만든것같습니다 ㅎㅎ..
이번엔 위젯을 간단하게 만들며 이전에 저장해 두었던 네이버 API키를 코드상에서 지웠습니다!
API-KEY 숨기기.
import Foundation
struct NaverStorage {
let naverClientID: String = "Your naverClientID"
let naverClientSecret: String = "Your naverClientSecret"
}
위처럼 키를 그대로 넣게되면 보안상의 문제가 많아 보입니다.. 별거 아닌 api이긴 하지만 그래도 조금은 신경써주는 것이 좋을것 같네요 ㅎㅎ..
Product -> Edit Scheme -> Run 부분의 Arguments에 Enviroment Variables + 를 클릭하여 apikey를 저장해줍니다.
이렇게 된다면 환경변수를 사용해서 API Key를 저장하였습니다.
- NaverNetwork.swift -
let headers: HTTPHeaders = [
"X-Naver-Client-Id": ProcessInfo.processInfo.environment["naverClientID"] ?? "",
"X-Naver-Client-Secret": ProcessInfo.processInfo.environment["naverClientSecret"] ?? ""
]
네이버 api를 연동하는 부분의 코드인 헤더 부분에 ProcessInfo.processInfo.environment["API-KEY"] 를 사용해 값을 가져오도록 합니다.
이렇게 한다면 코드상에 api키를 저장하지 않고 사용 할 수 있습니다.
위젯
다음은 간단한 위젯을 만들어보았습니다.
간단한 위젯이니 위젯의 사이즈는 2개로 하였습니다 많은 정보를 알려주는 앱은 아니니까요
systemSmall과 systemMedium의 크기만 제작하였습니다.
위젯을 제작하기전 기본 지식을 알고 가야겠죠..? 하하
위젯 생성은 Xcode -> File -> Target -> Widget Extension을 클릭후 이름을 입력하고 finish를 하면 됩니다.
(finish를 하기전 include Configuration Intent의 체크박스가 있습니다. 이 체크박스는 활성화 하게 된다면 사용자가 위젯 편집이 가능하게 합니다. 따라서 체크 하였습니다 ^_^)
위에서 입력한 이름의 위젯 파일로 가게된다면 어질어질한 코드들이 생성이 됩니다..(저도 처음이라 어질어질,,)
여러개의 struct들이 존재합니다!
- Provider :TimelineEntry
- SimpleEntry : TimelineEntry
- (위젯이름)EntryView : View
- (위젯이름) : Widget
이렇게 4개로 이루어져있습니다.
Provider
이 구조체는 위젯을 업데이트 할 시기를 WidgetKit에 알리는 역할을 한다고 합니다.
이 구조체엔 여러 메소드가 있습니다. ( placeholder, getSnapshot, getTimeline )
func placeholder
데이터를 불러오기 전 getSnapshot에 보여줄 placeholder라고 합니다.
func getSnapshot
getSnapshot은 위젯 갤러리에서 위젯을 고를때 보이는 샘플 데이터를 보여줄때 해당 메소드를 호출합니다.
func getTimeline
홈 화면에 있는 위젯을 업데이트하는 시간을 구현하는 부분 입니다.
let timeline = Timeline(entries: entries, policy: .atEnd)
이부분의 policy 부분에 들어가는 값은
.atEnd - 마지막 date가 끝난 후 타임라인 reloading
.after - 다음 date가 지난 후 타임라인 reloading
.never - 즉시/바로 타임라인 reloading
SimpleEntry
TimelineEntry를 준수하는 구조체이며 위젯을 표시할 Date를 정하고 그 Data에 표시할 데이터를 나타냅니다.
(위젯이름)EntryView : View
위젯을 나타낼 뷰의 코드입니다.
@Environment(\.widgetFamily) var family: WidgetFamily 를 사용하여 여러 위젯의 크기에 접근할 수 있습니다.
switch self.family {
case .systemSmall:
case .systemMedium:
...
(위젯이름) : Widget
위에서 설명된 메소드들이 호출되는 곳입니다. (+ @main)
struct WordQuizWidget: Widget {
let kind: String = "WordQuizWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
if #available(iOS 17.0, *) {
WordQuizWidgetEntryView(entry: entry)
.containerBackground(.fill.tertiary, for: .widget)
} else {
WordQuizWidgetEntryView(entry: entry)
.padding()
.background()
}
}
.configurationDisplayName("WordQuizDaily Widget")
.description("문해력을 높여주는 하루 한단어")
.supportedFamilies([.systemSmall,
.systemMedium])
}
}
저의 코드입니다.
body안의 IntentConfiguration, StaticConfiguration 2개로 설정할 수 있습니다.
IntentConfiguration은 사용자가 위젯에서 Edit을 통해 위젯에 보여지는 내용 변경이 가능합니다.
StaticConfiguration은 사용자가 변경 불가능한 정적 데이터를 보여줍니다.
(저는 단어를 보여주는 위젯이라 Static)
kind는 위젯의 ID입니다.
entry in 밑의 부분은 위젯에 보여질 뷰 입니다.
이제 위젯에 대한 개념을 알아보았고
저의 앱의 오늘의 단어와 뜻을 넘겨받아 위젯에 보여주는 일만 남았습니다 ^_^
ObservedObject를 사용해 변수의 내용을 받아오거나 userdefaults에 저장한 값을 가져오려는 하수같은 생각을 하였습니다만..ㅎㅎ
서치를통해 여러 블로그를 공부한 결과 App Extension Programming Guide를 참고하였고,
Extension과 App은 같은 컨테이너를 가지고 있지 않았습니다!
따라서 UserDefaults는 공유되지 않았습니다..
하지만 의지의 한국인 다른 방법을 제시해주신 착한 분들이 계셨습니다..
위의 Optional shared container를 사용해서 공유할 수 있었습니다.
App Group을 설정하여 데이터를 공유하는 방법이었습니다 ^_^
앱그룹의 컨테이너 이름은 group.(your_app's_bundle_id)로 해야 합니다. (하지만 저는 다르게 해버린걸..)
앱부분의 UserDefaults를 저장할 HomeViewModel 부분에서 appGroupId를 맞춰줍니다.
extension UserDefaults {
static var shared: UserDefaults {
let appGroupId = "group.wordQuizWidget"
return UserDefaults(suiteName: appGroupId)!
}
}
Extension부분인 위젯의 코드에서
let customUserDefaults = UserDefaults(suiteName: "group.wordQuizWidget")
으로 appGroupId를 맞춰주면
shared container를 사용한 UserDefaults를 공유하여 사용할 수 있습니다.
위젯 코드
//
// WordQuizWidget.swift
// WordQuizWidget
//
// Created by 정종원 on 3/7/24.
//
import WidgetKit
import SwiftUI
struct Provider: TimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date())
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date())
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
var entries: [SimpleEntry] = []
// Generate a timeline consisting of five entries an hour apart, starting from the current date.
let currentDate = Date()
for hourOffset in 0 ..< 5 {
let entryDate = Calendar.current.date(byAdding: .minute, value: hourOffset, to: currentDate)!
let entry = SimpleEntry(date: entryDate)
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
}
struct SimpleEntry: TimelineEntry {
let date: Date
}
struct WordQuizWidgetEntryView : View {
var entry: Provider.Entry
@Environment(\.widgetFamily) var family: WidgetFamily
var body: some View {
let customUserDefaults = UserDefaults(suiteName: "group.wordQuizWidget")
switch self.family {
case .systemSmall:
VStack{
if let storedWord = customUserDefaults?.string(forKey: "TodayWord"),
let storedDefinition = customUserDefaults?.string(forKey: "TodayWordDefinition") {
Spacer()
Text(storedWord)
.font(.largeTitle)
Spacer()
Text(storedDefinition)
.font(.footnote)
Spacer()
} else {
let _ = print("Nothing Printed")
}
}
case .systemMedium:
VStack{
if let storedWord = customUserDefaults?.string(forKey: "TodayWord"),
let storedDefinition = customUserDefaults?.string(forKey: "TodayWordDefinition") {
Spacer()
Text(storedWord)
.font(.largeTitle)
Spacer()
Text(storedDefinition)
.font(.footnote)
Spacer()
} else {
let _ = print("Nothing Printed")
}
}
default:
Text(".default")
}
}
}
struct WordQuizWidget: Widget {
let kind: String = "WordQuizWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
if #available(iOS 17.0, *) {
WordQuizWidgetEntryView(entry: entry)
.containerBackground(.fill.tertiary, for: .widget)
} else {
WordQuizWidgetEntryView(entry: entry)
.padding()
.background()
}
}
.configurationDisplayName("WordQuizDaily Widget")
.description("문해력을 높여주는 하루 한단어")
.supportedFamilies([.systemSmall,
.systemMedium])
}
}
#Preview(as: .systemSmall) {
WordQuizWidget()
} timeline: {
SimpleEntry(date: .now)
}
간단하게 systemSmall과 systemMedium 두개를 만들었습니다.
다음에 할것 -> 파이어베이스 연동하여, 앱푸시 기능 -> NotiView에서 시간대 설정 가능하게 하기.
지금 퀴즈뷰에서 네이버 이미지api로 관련 이미지를 가져오고 있는데 문제가 스포돼버리는 큰 이슈가 있습니다..
하지만 우리말샘에서 수어 이미지를 제공 해주더군요! 그래서 그 사진을 띄우면 어떨까... 합니다...
(다른분의 반응을 알고싶다,,ㅎ흐ㅡ긓ㄱ)
https://gyuios.tistory.com/102
https://www.masrinastudio.com/post/securing-api-keys-xcode-guide/
https://ios-development.tistory.com/1131
https://michael-kiley.medium.com/sharing-object-data-between-an-ios-app-and-its-widget-a0a1af499c31
'스터디 > 우리말 단어 퀴즈(SwiftUI)' 카테고리의 다른 글
우리말 퀴즈 앱)6. Local Notification 적용, API KEY 숨기기. (0) | 2024.06.25 |
---|---|
우리말 퀴즈 앱)5. urlSession을 async await으로 바꾸기.. (0) | 2024.05.19 |
우리말 퀴즈 앱)3.Alamofier, 네이버 검색 API 사용 (0) | 2024.02.23 |
우리말 퀴즈 앱)2.퀴즈뷰 구성중... (0) | 2024.01.29 |
우리말 퀴즈 앱)1.우리말샘 api (0) | 2024.01.25 |