ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [ios - SwiftUI] Widget 사용하기 (2/2)
    ios 2021. 2. 4. 15:37

     

     

     

    위 이미지는 뉴스 API를 이용해 받아온 데이터 리스트를 선택하면 해당 뷰로 이동할 수 있도록 제작한 위젯입니다.

     

    이번 글에서 코드를 살펴보도록 하겠습니다.

     

     

     

    struct myStaticWidgetEntryView : View {
        var entry: Provider.Entry
    
        var body: some View {
            VStack {
                Color(.systemOrange)
                Text("최신 뉴스 20")
            }
        }
    }
    struct myStaticWidgetEntryView : View {
        var entry: Provider.Entry
    
        var body: some View {
            ZStack {
                Color(.systemOrange)
                VStack {            
                    Text("최신 뉴스 20")
                }
            }
        }
    }

     

    뷰 백그라운드의 색상을 변경하기 위해선 ZStack에서 Color를 추가해 줘야 합니다. 

     

    아래 이미지를 통해 VStack과 ZStack에 배경색을 넣었을 때 차이를 확인할 수 있습니다.

     

     

     

    배경색을 변경했으니 이제 리스트를 추가하려고 하는데 위젯에서는 List를 지원하지 않는다고 합니다.

     

     

     

    리스트를 추가해보면 위와 같은 모습을 볼 수 있는데 이유를 찾아보니 위젯은 스크롤을 지원하지 않아서 사용할 수 없다는 것 같습니다. 

     

    위에서 사용한 VStack을 리스트처럼 사용하도록 합니다.

     

     

    struct RowView : View {
        let index: Int
        let name: String
        
        var body: some View {
            Text(name)
            Divider()
        }
    }
    

    리스트에 행에 해당하는 뷰를 만듭니다. 텍스트는 뉴스의 타이틀을 보여줄 예정이며

    Divider를 사용해 텍스트 아래 구분선을 나타냅니다.

     

     

    이제 위젯의 크기에 따라 리스트를 보여주도록 하겠습니다.

    struct SimpleEntry: TimelineEntry {
        let date: Date /// 필수 프로퍼티
        let news: News
    }

     

    TimelineEntry에 News Class을 추가해줬습니다. 사용하는 데이터에 따라 세팅해주면 됩니다. 

     

     

    func getNewData(completion: @escaping (News) -> ()) {
        let newsAddress: String = "http://newsapi.org/v2/top-headlines?country=kr&apiKey=75c9a397ffa14b8694f458a03c5342cf"  
        let task = URLSession.shared.dataTask(with: URL(string: newsAddress)!) { (data, response, errpr) in
            if let jsonData = data {     
                if let news = try? JSONDecoder().decode(News.self, from: jsonData) {
                    completion(news)
                }
            }
        }
        task.resume()
    }

     

    func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
        getNewData { (news) in
            let entry = SimpleEntry(date: Date(), news: news)
            completion(entry)
        }
    }

    위젯을 추가할 때 데이터를 미리 볼 수 있도록 하였습니다.

     

    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        getNewData { (news) in
            let date = Date()
            let entry = SimpleEntry(date: date, news: news)
            let nextUpdate = Calendar.current.date(byAdding: .minute, value: 15, to: date)
            let timeline = Timeline(entries: [entry], policy: .after(nextUpdate!))
                
            completion(timeline)
        }
    }

     

     

    struct myStaticWidgetEntryView : View {
        @Environment(\.widgetFamily) private var widgetFamily
        var entry: Provider.Entry
        
        var body: some View {
            ZStack {
                Color(.systemOrange)
                VStack {
                    switch widgetFamily {
                    case .systemSmall:
                        Text("최신 뉴스 \(entry.news.articles?.count ?? 0)").foregroundColor(.white)
                    case .systemMedium:
                        if let data = entry.news.articles {
                            ForEach(0..<4) { index in
                                RowView(index: index, name: data[index].title!).padding(EdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 10)).foregroundColor(.white)
                            }
                        }
                    case .systemLarge:
                        if let data = entry.news.articles {
                            ForEach(data, id:\.title) { item in
                                Text(item.title!).padding(EdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 10)).foregroundColor(.white)
                                Divider()
                            }
                        }
                    }
                }
            }
        }
    }

    위젯의 크기를 알기 위해서 @Environment(\. widgetFamily) private var widgetFamily를 선언 후 switch 문으로 분기합니다.

     

    ForEach를 사용해 News API로 얻은 리스트를 RowView로 추가하면 리스트가 완성됩니다.

     

     

     

     

    이제 홈화면에 위젯을 추가하고 로우를 선택 시 해당 선택된 로우의 정보를 뷰에서 확인할 수 있도록 하겠습니다.

     

     

     

    struct RowView : View {
        let index: Int
        let name: String
        
        var body: some View {
    
            let url = URL(string: "widget://key?Params=\(index)")!
            Link(destination: url) {
                Text(name)
                Divider()
            }
        }
        
    }

    Link를 추가해 각각의 리스트에서 앱의 appDelegate 또는 sceneDeleagate의 openURL 이동할 수 있도록 합니다.

     

    위젯 전체를 눌러 딥링킹 하고자 한다면 widgetURL을 사용하면됩니다.

     

    VStack {
        Text(name)
        Divider()
    }.widgetURL(URL(string: "widget://key?Params=\(index)"))
    

     

     

    이제 sceneDeleagate로 이동해서 아래 소스를 추가해 줍니다.

    func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
        if let url = URLContexts.first?.url {
            if url.absoluteString.starts(with: "widget://key") {
                guard let urlComponents = URLComponents(string: url.absoluteString) else { return }
                guard let requestParams = urlComponents.queryItems?.first(where: { $0.name == "Params" })?.value else { return }
                guard let requestValue = urlComponents.queryItems?.first(where: { $0.name == "Value" })?.value else { return }
                    
                let storyboard = UIStoryboard(name: "Main", bundle: nil)
                let vc = storyboard.instantiateViewController(withIdentifier: "ViewController2") as! ViewController2
                vc.data = requestParams
                    
                self.window?.rootViewController = vc
                self.window?.makeKeyAndVisible()
            }
        }
    }

     

    URL에 있던 index를 뷰 컨트롤 프로퍼티에 세팅하고 인덱스에 해당하는 뉴스를 보여주면 됩니다. 

     

    저는 인덱스만 표시하도록 했습니다.

     

    위젯에서 선택한 키를 확인

     

     

     

    @main
    struct myStaticWidget: Widget {
        let kind: String = "myStaticWidget"
    
        /// kind - Widget의 고유 식별자
        /// provider - 새로고침할 타임라인 결정
        
        /// StaticConfiguration 사용자가 프로퍼티를 구성할 수 없다. <- 그냥 보여주기용
        /// IntentConfiguration 사용자가 프로퍼티를 구성할 수 있다. <- 보고싶은것을 선택하는 것 등
        var body: some WidgetConfiguration {
            	
            StaticConfiguration(kind: kind, provider: Provider()) { entry in
                myStaticWidgetEntryView(entry: entry)
            }
            .configurationDisplayName("My Widget") // 위젯 추가, 편집시 표시 이름
            .description("This is an example widget.") // 위젯에 표시되는 설명
            .supportedFamilies([.systemSmall, .systemMedium, .systemLarge]) // 사이즈 지원
        }
    }
    

    위젯이 지원하는 사이즈를 선택하려면 supportedFamilies([.systemSmall, .systemMedium, .systemLarge])를 사용하면 됩니다.

    댓글

Designed by Tistory.