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
- combine
- clean architecture
- Observable
- tableView
- HIG
- uitableview
- ribs
- swift documentation
- 리펙터링
- SWIFT
- Protocol
- UITextView
- swiftUI
- rxswift
- UICollectionView
- Clean Code
- Human interface guide
- 스위프트
- ios
- Xcode
- Refactoring
- uiscrollview
- 리펙토링
- 클린 코드
- 애니메이션
- 리팩토링
- collectionview
- MVVM
- RxCocoa
- map
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - SwiftUI] 4. 위젯 Widget 사용 방법 - 위젯 이미지 로드 방법 본문
1. 위젯 Widget 사용 방법 - WidgetKit, WidgetFamily
2. 위젯 Widget 사용 방법 - API 데이터 로드와 위젯UI 업데이트
3. 위젯 Widget 사용 방법 - 위젯 딥링크 구현 방법 (widgetURL)
4. 위젯 Widget 사용 방법 - 위젯 이미지 로드 방법
5. 위젯 Widget 사용 방법 - Provisioning Profile 등록 (WidgetExtension)
위젯에 사진 로드 방법
- 이미지가 아닌 데이터는 상관 없지만, 위젯에서는 이미지를 async하게 로딩하는것을 지원하지 않으므로 sync하게 수행해야함을 주의
- 이미지 캐시를 사용하여, 딥링크 처리하는 쪽에서도 쉽게 처리되도록 구현
구현
- 예제 프로젝트 생성 -> 1번 포스팅 글에서 알아본 대로 WidgetKit Extension 추가
- 이미지를 캐싱하는 ImageCache.swift 파일 추가 (Targets에 MyWidgetExtension도 추가)
- ImageCache 구현
- 싱글톤으로 접근가능하도록 구현
import Foundation
import UIKit
final class ImageCache {
let shared = NSCache<NSString, UIImage>()
private init() {}
}
- Widget 예제 코드 준비
- 사용할 API: Flickr
https://api.flickr.com/services/feeds/photos_public.gne?tags=texas&tagmode=any&format=json&nojsoncallback=1
- 응답형식
{
"title": "Recent Uploads tagged texas",
"link": "https:\/\/www.flickr.com\/photos\/tags\/texas\/",
"description": "",
"modified": "2022-09-25T04:53:35Z",
"generator": "https:\/\/www.flickr.com",
"items": [
{
"title": "DSC_0239-10",
"link": "https:\/\/www.flickr.com\/photos\/jennsunique\/52381172512\/",
"media": {"m":"https:\/\/live.staticflickr.com\/65535\/52381172512_2269e63e8c_m.jpg"},
"date_taken": "2022-09-25T00:52:03-08:00",
"description": " <p><a href=\"https:\/\/www.flickr.com\/people\/jennsunique\/\">jennsunique<\/a> posted a photo:<\/p> <p><a href=\"https:\/\/www.flickr.com\/photos\/jennsunique\/52381172512\/\" title=\"DSC_0239-10\"><img src=\"https:\/\/live.staticflickr.com\/65535\/52381172512_2269e63e8c_m.jpg\" width=\"240\" height=\"160\" alt=\"DSC_0239-10\" \/><\/a><\/p> <p>Mayer Park, Spring, Texas<\/p>",
"published": "2022-09-25T04:53:35Z",
"author": "nobody@flickr.com (\"jennsunique\")",
"author_id": "23580355@N06",
"tags": "mayerpark spring texas nature trail cypresscreek creek walk park outdoors outside path jennifermcfarland"
},
{
"title": "DSC_0097-10",
"link": "https:\/\/www.flickr.com\/photos\/jennsunique\/52381173057\/",
"media": {"m":"https:\/\/live.staticflickr.com\/65535\/52381173057_f6bcb1fc83_m.jpg"},
"date_taken": "2022-09-25T00:52:24-08:00",
"description": " <p><a href=\"https:\/\/www.flickr.com\/people\/jennsunique\/\">jennsunique<\/a> posted a photo:<\/p> <p><a href=\"https:\/\/www.flickr.com\/photos\/jennsunique\/52381173057\/\" title=\"DSC_0097-10\"><img src=\"https:\/\/live.staticflickr.com\/65535\/52381173057_f6bcb1fc83_m.jpg\" width=\"240\" height=\"160\" alt=\"DSC_0097-10\" \/><\/a><\/p> <p>Mayer Park, Spring, Texas<\/p>",
"published": "2022-09-25T04:53:26Z",
"author": "nobody@flickr.com (\"jennsunique\")",
"author_id": "23580355@N06",
"tags": "mayerpark spring texas nature trail cypresscreek creek walk park outdoors outside path jennifermcfarland"
},
...
- Codable Model 정의
struct PhotoModel: Codable {
struct Item: Codable {
struct Media: Codable {
let m: String
}
let media: Media
}
let items: [Item]
}
extension PhotoModel {
var url: String? {
items.first?.media.m
}
}
- 3번 포스팅 글에서 알아본대로 기본적인 위젯 코드 구현
// MyWidget.swift
import WidgetKit
import SwiftUI
import Intents
struct Provider: IntentTimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date(), uiImage: UIImage(), url: "")
}
func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
getPhoto { uiImage, url in
let entry = SimpleEntry(date: Date(), uiImage: uiImage, url: url)
completion(entry)
}
}
func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
getPhoto { uiImage, url in
let currentDate = Date()
let entry = SimpleEntry(date: currentDate, uiImage: uiImage, url: url)
let nextRefresh = Calendar.current.date(byAdding: .minute, value: 3, to: currentDate)!
let timeline = Timeline(entries: [entry], policy: .after(nextRefresh))
completion(timeline)
}
}
private func getPhoto(completion: @escaping (UIImage, String) -> ()) {
// TODO
}
}
struct SimpleEntry: TimelineEntry {
let date: Date
let uiImage: UIImage
let url: String
}
struct MyWidgetEntryView : View {
var entry: Provider.Entry
var body: some View {
Image(uiImage: entry.uiImage)
.resizable()
.aspectRatio(contentMode: .fill)
.widgetURL(URL(string: getPercentEcododedString("widget://deeplink?url=\(entry.url)")))
}
private func getPercentEcododedString(_ string: String) -> String {
string.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? ""
}
}
@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("이미지를 불러오는 위젯 예제입니다")
}
}
- getPhoto(completion:) 부분구현
- API 호출
- 특정 url의 캐싱이 존재하면 ImageCache.shared.object(forKey:), 그 이미지 사용
- 캐싱이 안되어있으면, url에 해당하는 데이터를 불러온 후, uiImage로 변환
private func getPhoto(completion: @escaping (UIImage, String) -> ()) {
guard
let url = URL(string: "https://api.flickr.com/services/feeds/photos_public.gne?tags=texas&tagmode=any&format=json&nojsoncallback=1")
else { return }
URLSession.shared.dataTask(with: url) { data, response, error in
guard
let data = data,
let photoModel = try? JSONDecoder().decode(PhotoModel.self, from: data),
let urlString = photoModel.url
else { return }
if let uiImage = ImageCache.shared.object(forKey: urlString as NSString) {
completion(uiImage, urlString)
} else {
guard
let url = URL(string: urlString),
let data = try? Data(contentsOf: url),
let uiImage = UIImage(data: data)
else { return }
ImageCache.shared.setObject(uiImage, forKey: urlString as NSString)
completion(uiImage, urlString)
}
}.resume()
}
완성)
* 전체 코드: https://github.com/JK0369/ExPhotoWIdget
* 참고
https://stackoverflow.com/questions/63086029/ios-widgetkit-remote-images-fails-to-load
'iOS 응용 (SwiftUI)' 카테고리의 다른 글
Comments