SwiftUI制作简易的新闻列表Demo
import SwiftUI
struct HomeView: View {
@StateObject var newsViewModelVM = NewsViewModel()
var body: some View {
ZStack{
Group{
switch newsViewModelVM.state{
case .loading:
ProgressView()
case .failed(let error):
ErrorView(error: error) {
newsViewModelVM.getArticels()
}
case .success(let articels):
NavigationView{
List(articels){ item in
PostCellView(article: item)
}
.listStyle(.plain)
.navigationTitle(Text("News"))
}
}
}
.onAppear {
newsViewModelVM.getArticels()
}
Text("Desgin By Lujun 2022.2.8")
.font(.system(size: 30).bold())
.foregroundColor(.orange)
.shadow(color: .black, radius: 5, x: 5, y: 5)
.offset(y: 300)
}
}
}
struct NewsModel: Codable {
let articles: [Article]
}
struct Article: Codable,Identifiable {
var id = UUID()
let author: String?
let url: String?
let source, title, articleDescription: String?
let image: String?
let date: Date?
enum CodingKeys: String, CodingKey {
case author, url, source, title
case articleDescription = "description"
case image, date
}
}
错误自定义
enum APIError: Error {
case decodingError
case errorCode(Int)
case unknow
}
extension APIError: LocalizedError{
var errorDescription: String?{
switch self {
case .decodingError:
return "failurs"
case .errorCode(let code):
return "\(code) -somethinds error"
case .unknow:
return "the error in unknown"
}
}
}
请求数据流核心
import SwiftUI
import Combine
enum ResultState{
case loading
case success(content: [Article])
case failed(error: APIError)
}
let baseURL = "http://localhost:8081/"
class NewsViewModel: ObservableObject {
@Published var state: ResultState = .loading
private var aticels = [Article]()
private var cancelables = Set<AnyCancellable>()
func getArticels(){
let cancellable = request(from: "http://localhost:8081/news.json")
.sink { res in
switch res{
case .finished:
self.state = .success(content: self.aticels)
case .failure(let error):
self.state = .failed(error: error)
}
} receiveValue: { response in
self.aticels = response.articles
}
self.cancelables.insert(cancellable)
}
func request(from apiUrl: String) -> AnyPublisher<NewsModel,APIError>{
URLSession.shared
.dataTaskPublisher(for: URL(string: apiUrl)!)
.receive(on: DispatchQueue.main)
.mapError{_ in APIError.unknow}
.flatMap{data,response -> AnyPublisher<NewsModel,APIError> in
guard let response = response as? HTTPURLResponse else {
return Fail(error: APIError.unknow).eraseToAnyPublisher()
}
if (200...299).contains(response.statusCode) {
let jsonDeconder = JSONDecoder()
jsonDeconder.dateDecodingStrategy = .iso8601
return Just(data)
.decode(type: NewsModel.self, decoder: jsonDeconder)
.mapError{_ in APIError.decodingError}
.eraseToAnyPublisher()
}else{
return Fail(error: APIError.errorCode(response.statusCode)).eraseToAnyPublisher()
}
}
.eraseToAnyPublisher()
}
}
postCell核心
import SDWebImageSwiftUI
struct PostCellView: View {
let article: Article
var body: some View {
HStack{
if let imgUrl = article.image,
let url = URL(string: String(format: "%@%@", baseURL,imgUrl)) {
WebImage(url: url)
.placeholder(content: {
Color.gray
})
.resizable()
.scaledToFill()
.frame(width: 100, height: 100)
.cornerRadius(10)
}else{
Image(systemName: "photo.fill")
.foregroundColor(.white)
.background(Color.gray)
.frame(width: 100, height: 100)
.cornerRadius(10)
}
VStack(alignment: .leading, spacing: 4){
Text(article.title ?? "")
.foregroundColor(.black)
.font(.system(size: 18, weight: .semibold))
Text(article.source ?? "N/A")
.foregroundColor(.gray)
.font(.footnote)
}
}
.padding(.vertical
,10)
}
}
自定义错误View核心
struct ErrorView: View {
typealias ErrorViewActionHander = () -> Void
let hander : ErrorViewActionHander
internal init(error: Error,
hander: @escaping ErrorView.ErrorViewActionHander
){
self.error = error
self.hander = hander
}
let error: Error
var body: some View {
VStack{
Image(systemName: "exclamationmark.icloud.fill")
.foregroundColor(.gray)
.font(.system(size: 50, weight: .heavy))
Text("some error")
.font(.system(size: 30))
.fontWeight(.bold)
Text(error.localizedDescription)
.padding(4)
Button {
hander()
} label: {
Text("retry")
}
.foregroundColor(.white)
.padding(.vertical,17)
.padding(.horizontal,30)
.background(Color.blue)
.font(.system(size: 15, weight: .heavy))
.cornerRadius(10)
}
}
}
数据api来源 https://api.lil.software/news,需要未批嗯
|