[SwiftUI] WidgetClone: DeeplinkURL

Junyoung Park·2022년 12월 10일
0

SwiftUI

목록 보기
125/136
post-thumbnail
post-custom-banner

iOS 14 WidgetKit Pt 3 | Building COVID-19 API Stats | Widget Bundle & Deeplink URL

WidgetClone: DeeplinkURL

구현 목표

  • 유저 디폴트를 통한 서로 다른 타겟 앱 간의 그룹 연결
  • 딥링크 URL 구현

구현 태스크

  • 유저 디폴트 앱 그룹
  • 딥링크 URL → 컨텐츠 뷰 내 뷰 이동 핸들링

핵심 코드

extension UserDefaults {
    static var shared: UserDefaults {
        UserDefaults(suiteName: "group.junyeongPark.Covid19Stats") ?? .standard
    }
}
  • 각 타겟 엽 capabilities 내 앱 그룹을 설정
  • 해당 그룹명을 suiteName으로 사용한 공유용 유저 디폴트 설정
  • 타겟이 서로 다른 앱에서 동일한 컨텐츠 공유 가능
@Published private(set) var watchlist: [Country] = [] {
        didSet {
            save(countries: watchlist)
            WidgetCenter.shared.reloadTimelines(ofKind: "WatchlistStatsWidget")
        }
    }
  • ObservableObject로 선언한 뷰 모델 내 watchlist 퍼블리셔 값이 업데이트될 때마다 해당 값을 유저 디폴트 내에 저장 및 위젯 업데이트
 private func loadCountriesFromUserDefaults() -> [Country]? {
        let userDefaults = UserDefaults.shared
        guard
            let watchlist = userDefaults.object(forKey: "watchlist") as? Data,
            let countries = try? decoder.decode([Country].self, from: watchlist) else {
            return nil
        }
        return countries
    }
  • 위젯을 그릴 때 사용하는 loadCountriesFromUserDefaults 함수
  • 공유된 유저 디폴트로부터 데이터를 건네받아 사용자가 북마크에 추가한 나라 목록을 위젯 리스트화
var url: URL {
        let urlString = "stats://watchlist?id=\(id)&name=\(name)&iso=\(iso)".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
        guard
            let urlString = urlString,
            let url = URL(string: urlString) else { fatalError() }
        return url
    }
  • Country 자료 구조의 연산 프로퍼티로 추가된 url 프로퍼티
  • 기존 Country 데이터를 구성하는 프로퍼티를 쿼리로 가지고 있기 때문에 해당 URL 쿼리 아이템을 통해 해당 데이터 구성 가능
    var body: some View {
        ZStack {
            if entry.countryCases.isEmpty {
                Link(destination: url) {
                    Text("Add countries to watchlist from the App")
                        .foregroundColor(.blue)
                        .padding()
                }
            } else {
                VStack(spacing: 0) {
                    HStack {
                        Spacer()
                        Text("Watchlist")
                        Spacer()
                        Text(entry.date, style: .date)
                        Text(entry.date, style: .time)
                        Spacer()
                    }
                    .font(.system(size: 12, weight: .semibold))
                    .padding(.vertical, 4)
                    .padding(.horizontal)
                    Divider()
                    
                    ForEach(entry.countryCases.prefix(family == .systemMedium ? 2 : 4), id: \.country) { countryCase in
                        Link(destination: countryCase.country.url) {
                            if let caseStats = countryCase.caseStats {
                                WatchlistWidgetRowView(country: countryCase.country, caseStats: caseStats)
                            } else {
                                Text(countryCase.country.displayName)
                            }                        }
                        
                    }
                }
            }
        }
        .redacted(reason: entry.isPlaceholder ? .placeholder : .init())
    }
  • WatchlistStatsWidgetEntryView의 특정 아이템을 클릭할 때 해당 Country 아이템이 가지고 있는 URL 정보를 통해 딥링크 연결
        .sheet(item: $selectedCountry, onDismiss: {
            selectedCountry = nil
        }) { country in
            CountryDetailView(country: country)
        }
        .onOpenURL { url in
            selection = url.host ?? "summary"
            guard let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems else {
                self.selectedCountry = nil
                return
            }
            var dict = [String: String]()
            queryItems.forEach { item in
                dict[item.name] = item.value
            }
            guard
                let id = dict["id"],
                let name = dict["name"],
                let iso = dict["iso"]
            else {
                selectedCountry = nil
                return
            }
            
            self.selectedCountry = Country(id: id, name: name, iso: iso)
        }
  • 컨텐츠 뷰를 구성하는 해당 URL 열기 및 시트 구성
  • 링크를 통해 해당 앱을 열 때 쿼리 아이템을 통해 새로운 selectedCountry를 구성
  • 해당 @State 변수 값이 옵셔널이 아닐 때 곧바로 해당 값을 통해 파라미터로 넘겨준 디테일 뷰를 모달 시트로 띄우기

구현 화면

위젯을 통한 실제 뷰 링크 연결은 위젯의 편의성을 극대화한다!

profile
JUST DO IT
post-custom-banner

0개의 댓글