- Mar 18, 2025
Building a SwiftUI News App with NewsAPI: Step-by-Step Guide
- DevTechie
- SwiftUI
Today, we will walk through building a news app using SwiftUI and NewsAPI.org. SwiftUI makes it easy to create beautiful, responsive user interfaces with minimal code, and NewsAPI.org provides a simple way to fetch real-time news headlines from various sources. By combining these technologies, we will learn to make network requests, parse JSON data, and display articles in a clean, scrollable SwiftUI layout.
Before we start, visit https://newsapi.org/ and sign up for an account to get a free API key.
Next, let’s start by creating a new SwiftUI project and adding models for NewsAPI response.
Here’s how the JSON response from NewsAPI.org appears, and we’ll be organizing our data models based on this hierarchical structure.
News struct will serve as the model that will encapsulate the entirety of the API response.
struct News: Codable, Identifiable {
let id = UUID()
let status: String
let totalResults: Int
let articles: [Article]
enum CodingKeys: CodingKey {
case status
case totalResults
case articles
}
}Article struct represents the detailed information of an article and also contains data that we want to display on the main page of the app.
struct Article: Codable, Identifiable {
let id = UUID()
let source: Source
let author: String?
let title: String
let description: String?
let url: String
let urlToImage: String?
let publishedAt: String
let content: String?
enum CodingKeys: CodingKey {
case source
case author
case title
case description
case url
case urlToImage
case publishedAt
case content
}
}The Source struct represents the name and ID of the origin of a news article.
struct Source: Codable {
let id: String?a
let name: String
}Since this app relies on fetching news articles from an external source, we need to make network calls to retrieve the content from NewsAPI.org. To handle these calls efficiently, we will create a dedicated Network Manager that uses URLSession. This manager will be responsible for sending requests, handling responses, and decoding the JSON data into Swift model objects that our SwiftUI views can display.
enum HTTPMethod: String {
case get = "GET"
case post = "POST"
case put = "PUT"
case delete = "DELETE"
}
enum NetworkError: Error {
case invalidURL
case requestFailed(statusCode: Int)
case decodingError
case unknownError
}
struct NetworkingManager {
static let shared = NetworkingManager()
private init() {}
func request<T: Decodable>(
endpoint: String,
method: HTTPMethod = .get,
parameters: [String: Any]? = nil,
headers: [String: String]? = nil,
responseType: T.Type
) async throws -> T {
guard let url = URL(string: endpoint) else {
throw NetworkError.invalidURL
}
var request = URLRequest(url: url)
request.httpMethod = method.rawValue
headers?.forEach { key, value in
request.setValue(value, forHTTPHeaderField: key)
}
if let parameters = parameters, method != .get {
request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: [])
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
}
let (data, response) = try await URLSession.shared.data(for: request)
if let httpResponse = response as? HTTPURLResponse, !(200...299).contains(httpResponse.statusCode) {
throw NetworkError.requestFailed(statusCode: httpResponse.statusCode)
}
do {
return try JSONDecoder().decode(T.self, from: data)
} catch {
throw NetworkError.decodingError
}
}
}Next, we will extend our News model by adding an extension that handles fetching news articles directly from NewsAPI.org. This extension will contain a static function that constructs the appropriate API URL, including the endpoint and the required API key, to retrieve the latest technology news articles.
Refer to the official NewsAPI documentation here: https://newsapi.org/docs/endpoints/sources
extension News {
static func fetchNews() async -> News? {
do {
let news = try await NetworkingManager.shared.request(endpoint: "https://newsapi.org/v2/top-headlines?category=technology&country=us&apiKey=<<APIKEY>>", responseType: News.self)
return news
} catch {
print(error.localizedDescription)
}
return nil
}
}Next, we will begin working on the user interface, starting with CardView to display articles on the home page.
struct CardView: View {
var title: String
var desc: String
var author: String
var imageUrl: String
var body: some View {
VStack {
AsyncImage(url: URL(string: imageUrl)) { image in
image
.resizable()
.scaledToFit()
} placeholder: {
Image(systemName: "photo")
.resizable()
.scaledToFit()
}
.clipped()
VStack(alignment: .leading) {
Text(title)
.font(.headline)
Text(author)
.font(.subheadline)
.foregroundStyle(.secondary)
Text(desc)
.font(.caption)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
.padding(.leading)
}
.frame(maxWidth: .infinity)
.cornerRadius(30)
.background(
RoundedRectangle(cornerRadius: 30)
.foregroundStyle(.white)
.shadow(radius: 5)
)
.padding(10)
}
}We will use this CardView inside our main view to display each of the fetched news articles.
struct NewsUIExample: View {
@State private var news: News = .init(status: "", totalResults: 0, articles: [])
var body: some View {
NavigationStack {
List(news.articles) { article in
NavigationLink(value: article.url) {
CardView(title: article.title, desc: article.description ?? "", author: article.author ?? article.source.name, imageUrl: article.urlToImage ?? "")
}
}
.listStyle(.plain)
.navigationTitle("DT News")
.onAppear {
fetchNews()
}
.refreshable {
fetchNews()
}
.navigationDestination(for: String.self) { url in
WebView(url: URL(string: url)!)
}
}
}
func fetchNews() {
Task {
news = await News.fetchNews() ?? .init(status: "", totalResults: 0, articles: [])
}
}
}When a user taps on a news CardView, the app will open a WebView to display the full content of the selected news article.
import WebKit
struct WebView: UIViewRepresentable {
let url: URL
func makeUIView(context: Context) -> WKWebView {
return WKWebView()
}
func updateUIView(_ uiView: WKWebView, context: Context) {
let request = URLRequest(url: url)
uiView.load(request)
}
}Build and run

