Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- 스위프트
- collectionview
- UICollectionView
- MVVM
- Refactoring
- tableView
- 리펙터링
- Clean Code
- rxswift
- 클린 코드
- 애니메이션
- uitableview
- uiscrollview
- 리펙토링
- Protocol
- HIG
- Observable
- SWIFT
- 리팩토링
- ribs
- swiftUI
- combine
- ios
- map
- clean architecture
- UITextView
- Human interface guide
- Xcode
- RxCocoa
- swift documentation
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - SwiftUI] 3. 위젯 Widget 사용 방법 - 위젯 딥링크 구현 방법 (widgetURL, scenePhase, sheet) 본문
카테고리 없음
[iOS - SwiftUI] 3. 위젯 Widget 사용 방법 - 위젯 딥링크 구현 방법 (widgetURL, scenePhase, sheet)
jake-kim 2022. 10. 3. 23:271. 위젯 Widget 사용 방법 - WidgetKit, WidgetFamily
2. 위젯 Widget 사용 방법 - API 데이터 로드와 위젯UI 업데이트
3. 위젯 Widget 사용 방법 - 위젯 딥링크 구현 방법 (widgetURL, scenePhase, sheet)
4. 위젯 Widget 사용 방법 - 위젯 이미지 로드 방법
5. 위젯 Widget 사용 방법 - Provisioning Profile 등록 (WidgetExtension)
Widget 준비
- 2번 포스팅 글에서 구현한 위젯 코드 준비
import WidgetKit
import SwiftUI
import Intents
struct Provider: IntentTimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date(), texts: ["Empty"])
}
func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
getTexts { texts in
let entry = SimpleEntry(date: Date(), texts: texts)
completion(entry)
}
}
func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
getTexts { texts in
let currentDate = Date()
let entry = SimpleEntry(date: currentDate, texts: texts)
let nextRefresh = Calendar.current.date(byAdding: .minute, value: 3, to: currentDate)!
let timeline = Timeline(entries: [entry], policy: .after(nextRefresh))
completion(timeline)
}
}
// 메소드 추가
private func getTexts(completion: @escaping ([String]) -> ()) {
// https://github.com/wh-iterabb-it/meowfacts
guard
let url = URL(string: "https://meowfacts.herokuapp.com/?count=1")
else { return }
URLSession.shared.dataTask(with: url) { data, response, error in
guard
let data = data,
let textModel = try? JSONDecoder().decode(TextModel.self, from: data)
else { return }
completion(textModel.datas)
}.resume()
}
}
struct SimpleEntry: TimelineEntry {
let date: Date
let texts: [String]
}
// 모델 추가
struct TextModel: Codable {
enum CodingKeys : String, CodingKey {
case datas = "data"
}
let datas: [String]
}
struct MyWidgetEntryView : View {
var entry: Provider.Entry
private var randomColor: Color {
Color(
red: .random(in: 0...1),
green: .random(in: 0...1),
blue: .random(in: 0...1)
)
}
var body: some View {
ZStack {
randomColor.opacity(0.7)
ForEach(entry.texts, id: \.hashValue) { text in
LazyVStack { // Widget은 스크롤이 안되므로, List지원 x (대신 VStack 사용)
Text(text)
.foregroundColor(Color.white)
.lineLimit(1)
Divider()
}
}
}
}
}
@main
struct MyWidget: Widget {
let kind: String = "MyWidget"
var body: some WidgetConfiguration {
IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
MyWidgetEntryView(entry: entry)
}
.configurationDisplayName("위젯 예제")
.description("랜덤 텍스트를 불러오는 위젯 예제입니다")
}
}
struct MyWidget_Previews: PreviewProvider {
static var previews: some View {
MyWidgetEntryView(entry: SimpleEntry(date: Date(), texts: ["empty"]))
.previewContext(WidgetPreviewContext(family: .systemSmall))
}
}
위젯 딥링크 처리 - 송신하는쪽
- Widget 코드에서 뷰에다가 .widgetURL(_:)를 선언하여 딥링크 송신
struct MyWidgetEntryView : View {
...
var body: some View {
ZStack {
randomColor.opacity(0.7)
ForEach(entry.texts, id: \.hashValue) { text in
LazyVStack {
Text(text)
.foregroundColor(Color.white)
.lineLimit(1)
.widgetURL("여기서 딥링킹") // <-
}
}
}
}
}
- widgetURL에 주입될 딥링크 포멧 정의
- scheme: widget
- host: deeplink
- query: text
widget://deeplink?text={content}
- query에 text를 넣는데, text에는 띄어쓰기와 한글이 있으면 URL 변환에 실패하므로 percent encoding이 필요
- percent encoding 관련 개념은 이전 포스팅 글 참고
private func getPercentEcododedString(_ string: String) -> String {
string.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? ""
}
- 위 함수를 사용
Text(text)
.foregroundColor(Color.white)
.lineLimit(1)
.widgetURL(URL(string: getPercentEcododedString("widget://deeplink?text=\(text)")))
딥링크 처리 - 수신하는쪽
- @main 엔트리 포인트인 곳의 WindowGroup 하위에 있는 뷰 바로 밑에 onOpenURL 을 추가하여 처리
- 보낼때 percent encoding을 했으므로 받는곳에서 removingPercentEncoding을 하여 다시 변환
@main
struct RandomTextApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL { url in
let text = url.absoluteString.removingPercentEncoding ?? ""
}
}
}
}
- 이 url을 ContentView에 전달시켜주어야 하는데, 하위 뷰들에게 전달할때 가장 좋은 방법인 @Environment 사용
- @Environment 사용하는 구체적인 개념은 이전 포스팅 글 참고
- @Environment 커스텀 키 정의
- EnvironmentKey를 준수하는 DeepLinkEnv 구조체 정의
struct DeepLinkEnv: EnvironmentKey {
static let defaultValue = ""
}
- @Environment 커스텀 Values 정의
- 프로퍼티는 deepLinkText
extension EnvironmentValues {
var deepLinkText: String {
get { self[DeepLinkEnv.self] }
set { self[DeepLinkEnv.self] = newValue }
}
}
- 적용 방법
- @main 구조체에 하위에 값을 던져줄 @State 변수 선언
@main
struct RandomTextApp: App {
@State var text: String = "" // <-
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL { url in
text = url.absoluteString.removingPercentEncoding ?? ""
}
}
}
}
- 하위 뷰인 ContentView에 넘겨주기 위해서 .environment()를 추가하여 전달하고 onOpenURL에서 text 입력
@main
struct RandomTextApp: App {
@State var text: String = ""
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.deepLinkText, text) // <-
.onOpenURL { url in
text = url.absoluteString.removingPercentEncoding ?? "" // <-
}
}
}
}
- ContentView에서는 @Environment를 선언하여, 적용
struct ContentView: View {
@Environment(\.deepLinkText) var deepLinkText: String
var body: some View {
if deepLinkText.isEmpty {
Text("Hello World")
} else {
Text(deepLinkText)
}
}
}
완성)
딥링크 + .sheet
- 딥링크가 오면 그에 해당하는 페이지를 띄워주는 형태로 구현 방법?
- isPresented가 true이면 sheet가 표출되고, isPresented는 onOpenURL안에서 상태의 값을 변경하도록 처리
- 필요한 상태 변수 선언
@main
struct RandomTextApp: App {
@State var text: String = ""
@State var isPresented: Bool = false
}
- body에서 딥링크 처리
- onOpenURL: 딥링크 수신 처리, 여기서 isPresented = true로 설정
- .sheet(): isPresented에 상태에 따라 딥링크 데이터를 표출할지 결정 (dismiss시에 딥링크 데이터를 초기화)
@main
struct RandomTextApp: App {
@State var text: String = ""
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.deepLinkText, text)
.onOpenURL { url in
text = url.absoluteString.removingPercentEncoding ?? ""
isPresented = true
}
.sheet(
isPresented: $isPresented,
onDismiss: {
text = ""
},
content: {
if !text.isEmpty {
Text(text)
.font(.title)
.foregroundColor(.primary)
}
}
)
}
}
}
cf) 꼭 최상위 뷰인 @main에서 onOpenURL을 놓지 않고, 어느 뷰에서든 onOpenURL을 붙여서 편리하게 처리가 가능
* 전체 코드: https://github.com/JK0369/ExRandomText/tree/deepLink
Comments