티스토리 뷰
News의 api data를 parsing하여 SwiftUI list에 뿌려주기!
를 해볼건데, 기존 Swift에는 없는 SwiftUI만의 바인딩 특성을 사용하여 데이터를 뿌려보겠습니다.
https://newsapi.org/ 의 뉴스 api로 작업해보겠습니다.
SwiftUI에서는 내장되어있는 combine이라는 언어의
observableObject를 사용하여 fetch한 data를 뿌려주는 방법입니다.
소스코드는 맨밑에 깃헙주소있어요
data를 가져오는 RequestAPI를 구현합니다.
import Foundation
class RequestAPI {
static let shared = RequestAPI()
private init() { }
private let apiKey = Bundle.main.object(forInfoDictionaryKey: "API_KEY") as? String
func fetchData(){
guard let apiKey = apiKey else { return }
guard let url = URL(string: "https://newsapi.org/v2/top-headlines?country=kr&apiKey=\(apiKey)") else{
return
}
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { data, response, error in
if let error = error{
print(error.localizedDescription)
return
}
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else{
// 실패
return
}
guard let data = data else{
return
}
do{
let apiResponse = try JSONDecoder().decode(Results.self, from: data)
// 성공
}catch(let err){
print(err.localizedDescription)
}
}
task.resume()
}
}
이렇게 가져온 data를
스유는 @Published, state를 사용하여 간단하게 뿌려주기가 가능합니다.
1. 프로토콜 ObservableObject를 채택합니다.
class RequestAPI: ObservableObject { .. }
2. @Published
data가 변할때마다 view를 update하려고 하는 data에 @published를 붙여줍니다.
@Published var posts = [Article]()
@Published의 기능은 해당 값이 변할 때 마다 observer에게 변경사항을 전달할 수 있게 합니다.
observer는 알림이 오면 view를 update하도록 합니다.
import Foundation
class RequestAPI: ObservableObject {
static let shared = RequestAPI()
private init() { }
@Published var posts = [Article]()
private let apiKey = Bundle.main.object(forInfoDictionaryKey: "API_KEY") as? String
func fetchData(){
guard let apiKey = apiKey else { return }
guard let url = URL(string: "https://newsapi.org/v2/top-headlines?country=kr&apiKey=\(apiKey)") else{
return
}
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { data, response, error in
if let error = error{
print(error.localizedDescription)
return
}
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else{
self.posts = []
return
}
guard let data = data else{
return
}
do{
let apiResponse = try JSONDecoder().decode(Results.self, from: data)
DispatchQueue.main.async {
self.posts = apiResponse.articles
}
}catch(let err){
print(err.localizedDescription)
}
}
task.resume()
}
}
3. 마지막으로 알림이 올때 마다 view를 update시키기 위해, view에 쓰이는 값인 ObservableObject를 인스턴스화 합니다 ( @State )
update하고자 하는 view에 연결된 값에 @State를 붙여주면 됩니다.
@State 애플 문서입니다
1. @State는 값 자체가 아니라 값을 읽고 쓰는 수단이다.
2. @State는 private와 한 쌍이라고 해요. 이유는 view 외부에서 사용되지 않아야 한다고 합니다.
(목적이 view를 업데이트 되도록 하는 기능이므로)
+ 다른 뷰 계층에서 사용 하려면 $붙이면 된다고 합니다. (애플 예제)
PlayeView의 state 변수 isPlaying을 PlayButton라는 다른 뷰에 써주려고 $붙여줌
저의 예제입니다. (News data를 fetch하고 fetch한 data를 view에 binding)
import SwiftUI
struct ContentView: View {
@StateObject private var network = RequestAPI.shared
var body: some View {
NavigationView{
List{
ForEach(network.posts, id: \.self) { result in
Text(result.title)
}
}.navigationTitle("뉴스 둘러보기")
}.onAppear {
network.fetchData()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
헌데 class 형태 이므로 @StateObject로 해준 모습.
이렇게 해주니 잘 나옵니다.
잘 나옵니다.
JsonDecoder Model도 딕셔너리 형태이므로, Hashable을 채택합니다.
import Foundation
// MARK: - Results
struct Results: Decodable {
let articles: [Article]
}
// MARK: - Article
struct Article: Decodable, Hashable {
let title: String
let url: String
let urlToImage: String?
}
그럼 이제 바인딩은 끝 입니다.
이미지도 추가해보겠습니다.
//
// ContentView.swift
// SwiftUINewsAPP
//
// Created by jh on 2021/10/26.
//
import SwiftUI
struct URLImage: View {
let urlString: String?
@State var data: Data?
var body: some View {
if let data = data, let uiimage = UIImage(data: data) {
Image(uiImage: uiimage)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 130, height: 70)
.background(Color.white)
} else {
Image("")
.frame(width: 130, height: 70)
.background(Color.gray)
.onAppear {
fetchImageData()
}
}
}
private func fetchImageData(){
guard let urlString = urlString else{
return
}
guard let url = URL(string: urlString) else{
return
}
let task = URLSession.shared.dataTask(with: url) { data, _, _ in
self.data = data
}
task.resume()
}
}
struct ContentView: View {
@StateObject private var network = RequestAPI.shared
var body: some View {
NavigationView{
List{
ForEach(network.posts, id: \.self) { result in
HStack{
URLImage(urlString: result.urlToImage)
.frame(width: 130, height: 70)
.background(Color.gray)
Text(result.title)
.bold()
}.padding(3)
}
}.navigationTitle("뉴스 둘러보기")
}.onAppear {
network.fetchData()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
이미지를 담는 data 변수를 @State로 선언해주고,
회색배경의 빈 이미지뷰가 onApear될 때, fetchImageData()로 변수 data에 fetch한 값을 저장해줍니다.
그러면 View가 새로 그려지면서, fetch한 image data로 Image를 갱신합니다.
이렇게 잘 나오는 모습입니다.
여기까지 swiftUI로 new API를 받아 Observable과 binding으로 fetch한 데이터를 뿌려주기 였습니다.
https://github.com/JangJaeHyung1/SwiftUINewsApp
부족한 글 읽어주셔서 감사합니다
수정해주실 내용 있으시면 남겨주시면 감사드리겠습니다 :)
- Total
- Today
- Yesterday
- swift 엑셀 읽기
- swift 자간
- llm pdf rag
- swift urlcomponent encode
- readysay
- deep timer
- swift urlsession module
- swift excel read
- swift urlsession network module
- rag 기반 llm 챗봇
- 엔디소프트 레이세이
- llm csv
- 레디세이 어플
- swift network 공통화
- swift urlsession refactoring
- filemanager excel read
- chatgpt rag llm
- rag 기반 llm
- swift queryitem encode
- swift urlsession 공통화
- swift get excel
- swift filemanager excel
- swift filemanager get excel
- swift 엑셀 가져오기
- swift network refactoring
- 레디세이
- rag llm pdf
- focus timer 어플
- swift network module
- swift 네트워크 모듈화
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |