News Widget by SwiftUI

Step_01: Get the news api by login this website: https://newsapi.org
Then you will get same this api “09510d0f4f3641258f6f703aa2ef9611
After get the api key, now hit the api in Postman and you will see the JSON response data same as below:

{
  "status": "ok",
  "totalResults": 24,
  "articles": [
    {
      "source": {
        "id": "the-wall-street-journal",
        "name": "The Wall Street Journal"
      },
      "author": "Jean Eaglesham",
      "title": "Climate Promises by Businesses Face New Scrutiny",
      "description": "Emissions pledges from U.N. Glasgow conference will be closely watched",
      "url": "https://www.wsj.com/articles/climate-promises-by-businesses-face-new-scrutiny-11636104600?mod=hp_lead_pos2",
      "urlToImage": "https://images.wsj.net/im-429222/social",
      "publishedAt": "2021-11-05T09:30:00Z",
      "content": "The United Nations conference on climate change in Glasgow has been full of promises by companies to reduce their carbon emissions. How many will live up to them and how will anyone know if they are … [+333 chars]"
    },
    {
      "source": {
        "id": "the-wall-street-journal",
        "name": "The Wall Street Journal"
      },
      "author": "Jean Eaglesham",
      "title": "Climate Promises by Businesses Face New Scrutiny",
      "description": "Emissions pledges from U.N. Glasgow conference will be closely watched",
      "url": "https://www.wsj.com/articles/climate-promises-by-businesses-face-new-scrutiny-11636104600?mod=hp_lead_pos2",
      "urlToImage": "https://images.wsj.net/im-429222/social",
      "publishedAt": "2021-11-05T09:30:00Z",
      "content": "The United Nations conference on climate change in Glasgow has been full of promises by companies to reduce their carbon emissions. How many will live up to them and how will anyone know if they are … [+333 chars]"
    }
    ]
  }

Step_02: Now create new project selecting iOS target choosen project name:

Step_03: Create new swift file name NewsArticleList & NewsArticle and also added below code:

struct NewsArticleList: Codable {
    var status: String
    var totalResults: Int
    var articles: [NewsArticle]
}
struct NewsArticle: Codable {
    var author: String?
    var title: String
    var description: String?
    var url: String?
    var urlToImage: String?
    var content: String?    
    var publishedAt: String?
}
extension NewsArticle: Identifiable {
    var id: String {
        return title
    }
}

Step_04: Create APIService class for In order to retrieve the data from News API, we need a simple API service which will retrieve the JSON and convert it to a Codable Dataset. So create the following:

final class APIService {
    enum APIError: Error {
        case unknownError
    }

    func getObject<T: Codable>(object: T.Type, url: URL) -> AnyPublisher<T, Error> {
        return URLSession.shared.dataTaskPublisher(for: url)
                .tryMap { (data, response) -> Data in
                    guard let httpResponse = response as? HTTPURLResponse else {
                        throw APIError.unknownError
                    }
                    guard 200...299 ~= httpResponse.statusCode else {
                        throw URLError(URLError.Code(rawValue: httpResponse.statusCode), userInfo: httpResponse.allHeaderFields as? [String: Any] ?? [:] )
                    }
                    return data
                }
                .decode(type: T.self, decoder: JSONDecoder())
                .eraseToAnyPublisher()
    }
}

Step_05:  For that, we create a view model for retrieving the data from News API with an observable state, which will contain the data.

import Foundation
import Combine

final class NewsViewModel: ObservableObject {

    // MARK: - Constants
    private enum Constants {
      static let endpointString = "https://newsapi.org/v2/top-headlines?country=us&sortBy=publishedAt"
      static let apiKey = "09510d0f4f3641258f6f703aa2ef9612"
    }

    enum Category: String {
       case general
       case business
       case entertainment
       case health
       case science
       case sports
       case technology
   }

    // MARK: - state
    enum ResultState {
        case loading
        case error(Error)
        case success([NewsArticle])
    }

    // MARK: - properties
    private var cancelables: Set<AnyCancellable> = Set<AnyCancellable>()
    private let apiService: APIService

    @Published var state: ResultState = .loading

    init(apiService: APIService = APIService() ) {
        self.apiService = apiService
    }

    func getDataIfNeeded(category: Category = .general) {
         guard let url = getUrlForCategory(category: category) else {
             //completion?(.failure(APIService.APIError.unknownError))
             return
         }

         state = .loading
         apiService.getObject(object: NewsArticleList.self, url: url)
             .subscribe(on: DispatchQueue.global(qos: .background))
             .receive(on: DispatchQueue.main)
             .sink { [weak self] result in
                 switch result {
                 case .failure(let error):
                     self?.state = .error(error)
                     //completion?(.failure(error))
                 default: break
                 }

             } receiveValue: { [weak self] list in
                 self?.state = .success(list.articles)
             }.store(in: &cancelables)
     }

     private func getUrlForCategory(category: Category = .general) -> URL? {
          var urlComponents = URLComponents(string: Constants.endpointString)
          var queryItems = urlComponents?.queryItems
          queryItems?.append(URLQueryItem(name: "category", value: category.rawValue))
          queryItems?.append(URLQueryItem(name: "apiKey", value: Constants.apiKey))
          urlComponents?.queryItems = queryItems

          return urlComponents?.url
     }

}

Step_06: Now added below code in you ContentView for UI:

import SwiftUI
import Combine


struct ContentView: View {

    @ObservedObject var newsViewModel = NewsViewModel()

    var body: some View {
        GeometryReader { geometry in

            switch newsViewModel.state {
            case .loading:
                Text("Loading...")
                    .frame(width: geometry.size.width, height: geometry.size.height, alignment: .center)
            case .success(let result):
                List(result) { item in
                    Text(item.title)
                }
            case .error:
                Text("Error occured!")
                    .frame(width: geometry.size.width, height: geometry.size.height, alignment: .center)
            }

            }.onAppear {
                newsViewModel.getDataIfNeeded()
            }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Now if you run your project you will see news list of data. That was our general iPhone OS part. Now we will make widget & show data in widget fetching from NewsAPI data.

Widget_Step_01: Now we will create Widget Extension. Select project file then click File->New->Target. select iOS item and then search for widget extension.
For get the news value, we need to set target NewsWidgetExtension for all of classes:
1. NewsArticle
2. NewsArticleList
3. APIServices
4. NewsViewModel

Widget_Step_02: Modify NewsViewModel class like below:

import Foundation
import Combine

final class NewsViewModel: ObservableObject {

    // MARK: - Constants
    private enum Constants {
      static let endpointString = "https://newsapi.org/v2/top-headlines?country=us&sortBy=publishedAt"
      static let apiKey = "09510d0f4f3641258f6f703aa2ef9612"
    }

    // MARK: - properties
    enum Category: String {
        case general
        case business
        case entertainment
        case health
        case science
        case sports
        case technology
    }

    enum ResultState {
        case loading
        case error(Error)
        case success([NewsArticle])
    }

    private var cancelables: Set<AnyCancellable> = Set<AnyCancellable>()
    private let apiService: APIService

    @Published var state: ResultState = .loading

    // MARK: - init
    init(apiService: APIService = APIService() ) {
        self.apiService = apiService
    }

    // MARK: - get data
    func getDataIfNeeded(category: Category = .general, completion: ((Result<[NewsArticle], Error>) -> Void )? = nil ) {
        guard let url = getUrlForCategory(category: category) else {
            completion?(.failure(APIService.APIError.unknownError))
            return
        }

        state = .loading
        apiService.getObject(object: NewsArticleList.self, url: url)
            .subscribe(on: DispatchQueue.global(qos: .background))
            .receive(on: DispatchQueue.main)
            .sink { [weak self] result in
                switch result {
                case .failure(let error):
                    self?.state = .error(error)
                    completion?(.failure(error))
                default: break
                }

            } receiveValue: { [weak self] list in
                self?.state = .success(list.articles)
                completion?(.success(list.articles))
            }.store(in: &cancelables)
    }

    private func getUrlForCategory(category: Category = .general) -> URL? {
         var urlComponents = URLComponents(string: Constants.endpointString)
         var queryItems = urlComponents?.queryItems
         queryItems?.append(URLQueryItem(name: "category", value: category.rawValue))
         queryItems?.append(URLQueryItem(name: "apiKey", value: Constants.apiKey))
         urlComponents?.queryItems = queryItems

         return urlComponents?.url
    }

}

Widget_Step_03: In NewsWidget struck Refactor SimpleEntry by NewsListEntry. And added below code. If you see any error configuration then fixed it by state case set .idle:

struct NewsListEntry: TimelineEntry {
    enum State{
        case idle
        case error
        case success([NewsArticle])
    }
    let date: Date
    let state: State
}

Widget_Step_04: After successfully build the app, now create NewsViewModel object in Provider struct. Call the api for get data in timeline function and added code same as below:

struct Provider: AppIntentTimelineProvider {
    
    private let newsViewModel: NewsViewModel = NewsViewModel()
    
    func placeholder(in context: Context) -> NewsListEntry {
        NewsListEntry(date: Date(), state: .idle)
    }

    func snapshot(for configuration: ConfigurationAppIntent, in context: Context) async -> NewsListEntry {
        NewsListEntry(date: Date(), state: .idle)
    }
    
    func timeline(for configuration: ConfigurationAppIntent, in context: Context) async -> Timeline<NewsListEntry> {
        var entries: [NewsListEntry] = []
        newsViewModel.getDataIfNeeded{ result in
            switch result {
            case .success(let newsItems):
                let items = Array(newsItems.prefix(4))
                entries.append(NewsListEntry(date: Date(), state: .success(items)))
            case .failure:
                entries.append(NewsListEntry(date: Date(), state: .error))
                break
            }
        }
        return Timeline(entries: entries, policy: .atEnd)
    }
}

Widget_Step_05: Now set your NewsWidgetEntryView below like:

struct NewsWidgetEntryView : View {
    var entry: NewsListEntry

    var body: some View {
        GeometryReader { geometry in
            switch entry.state {
            case .idle:
                Text("No data yet, waiting")
                    .font(.system(size: 12))
                    .frame(width: geometry.size.width, height: geometry.size.height, alignment: .center)
            case .error:
                Text("Oops something went wrong - please reconfigure")
                    .font(.system(size: 12))
                    .frame(width: geometry.size.width, height: geometry.size.height, alignment: .center)
            case .success(let array):
                VStack {
                    Text("My News Widget")
                    Spacer()
                    ForEach(array) { array in
                        Text(array.title)
                            .frame(maxWidth: geometry.size.width, alignment: .leading)
                            .font(.system(size: 12))
                            .multilineTextAlignment(.leading)
                    }
                    Spacer()
                }
            }
        }
    }
}

71 thoughts on “News Widget by SwiftUI

  • 15/04/2024 at 6:00 PM
    Permalink

    Wow, fantastic weblog structure! How long have you ever been running
    a blog for? you made blogging look easy. The whole glance of
    your site is wonderful, as smartly as the content! You can see similar here sklep

    Reply
  • 03/07/2024 at 9:21 AM
    Permalink

    Полностью трендовые события подиума.
    Абсолютно все эвенты мировых подуимов.
    Модные дома, лейблы, высокая мода.
    Новое место для трендовых людей.
    https://hypebeasts.ru/

    Reply
  • 12/07/2024 at 11:38 PM
    Permalink

    Полностью актуальные события модного мира.
    Актуальные новости известнейших подуимов.
    Модные дома, торговые марки, гедонизм.
    Лучшее место для модных хайпбистов.
    https://modastars.ru/

    Reply
  • 16/07/2024 at 7:19 AM
    Permalink

    Полностью важные новости подиума.
    Важные новости лучших подуимов.
    Модные дома, лейблы, высокая мода.
    Приятное место для модных людей.
    https://donnafashion.ru/

    Reply
  • 19/07/2024 at 3:36 AM
    Permalink

    Наиболее свежие события индустрии.
    Исчерпывающие события лучших подуимов.
    Модные дома, лейблы, высокая мода.
    Новое место для трендовых людей.
    https://donnafashion.ru/

    Reply
  • 27/07/2024 at 12:49 PM
    Permalink

    Абсолютно стильные новинки индустрии.
    Важные новости мировых подуимов.
    Модные дома, торговые марки, высокая мода.
    Интересное место для стильныех людей.
    https://lecoupon.ru/

    Reply
  • 18/10/2024 at 3:03 PM
    Permalink

    Бренд Balenciaga является выдающимся домов высокой моды, который создался в начале 20 века легендарным кутюрье Кристобалем Баленсиагой. Его узнают уникальными моделями и необычными силуэтами, противоречат стандартам индустрии.
    https://balenciaga.whitesneaker.ru/

    Reply
  • 28/10/2024 at 4:02 PM
    Permalink

    Вещи бренда Fendi представлены у нас. Широкий ассортимент Fendi поможет подобрать идеальные вещи для любого случая.
    https://fendi.sneakerside.ru

    Reply
  • 30/10/2024 at 11:01 PM
    Permalink

    Bottega Veneta — это престижный итальянский бренд, известный неповторимым стилем. Основанный в 1966 году, бренд стал символом стиля и элегантности и славится классическими аксессуарами. Дизайны бренда отражают искусность и внимательность к деталям, а также уникальный подход к дизайну.
    https://bottega-official.ru

    Reply
  • 02/11/2024 at 7:48 AM
    Permalink

    Официальный интернет-магазин Боттега Венета предлагает разнообразие изделий премиум-класса от итальянского бренда. В нашем каталоге вы сможете выбрать и заказать изделия последних поступлений с возможностью доставки по Москве и всей России.
    https://bottega-official.ru

    Reply
  • 12/01/2025 at 9:26 AM
    Permalink

    На данном сайте вы найдёте полезную информацию о терапии депрессии у пожилых людей. Вы также узнаете здесь о профилактических мерах, актуальных подходах и рекомендациях специалистов.
    http://forum.zplatformu.com/index.php?topic=296550.new

    Reply
  • 23/01/2025 at 2:11 AM
    Permalink

    This cutting-edge CCTV software delivers a robust video surveillance solution, offering intelligent detection capabilities for people, cats, birds, and dogs. As a comprehensive surveillance camera software, it functions as an IP camera recorder and includes time-lapse recording. CCTV Software Enjoy secure remote access to your IP camera feeds through a reliable cloud video surveillance platform. This video monitoring software enhances your security system and is an excellent option for your CCTV monitoring needs.

    Reply
  • 25/01/2025 at 4:16 AM
    Permalink

    На данном ресурсе вы сможете получить работающее альтернативный адрес 1xBet.
    Здесь предоставляем только актуальные ссылки для доступа.
    Если главный портал заблокирован, примените зеркалом.
    Будьте постоянно в игре без ограничений.
    https://telegra.ph/Otvetstvennaya-igra-kak-poluchat-udovolstvie-bez-riskov-01-21

    Reply
  • 25/01/2025 at 3:27 PM
    Permalink

    На данном сайте можно найти информацией о телешоу “Однажды в сказке”, развитии событий и главных персонажах. https://odnazhdy-v-skazke-online.ru/ Здесь представлены интересные материалы о производстве шоу, исполнителях ролей и фактах из-за кулис.

    Reply
  • 29/01/2025 at 12:32 AM
    Permalink

    На этом сайте можно ознакомиться с информацией о сериале “Однажды в сказке”, развитии событий и ключевых персонажах. https://odnazhdy-v-skazke-online.ru/ Здесь представлены подробные материалы о создании шоу, исполнителях ролей и фактах из-за кулис.

    Reply
  • 30/01/2025 at 7:13 AM
    Permalink

    This extensive resource serves as an thorough guide to the world of modern video surveillance, providing valuable insights for both skilled CCTV installers and business owners seeking to enhance their protection systems.
    Lux Security Software
    The site presents a detailed analysis of cloud-based video surveillance systems, exploring their benefits, drawbacks, and effective applications.

    Reply
  • 01/02/2025 at 1:31 PM
    Permalink

    На этом сайте размещены последние новости РФ и мира .
    Здесь можно прочитать значимые репортажи по разным темам .
    https://ecopies.rftimes.ru/
    Читайте важнейших новостей каждый день .
    Проверенная информация и оперативность в каждом материале .

    Reply
  • 02/02/2025 at 3:50 PM
    Permalink

    On this website, you will find details about the 1Win gambling platform in Nigeria.
    It covers key features, including the popular online game Aviator.
    https://1win-casino-ng.com/
    You can also explore sports wagering opportunities.
    Enjoy an exciting gaming experience!

    Reply
  • 14/02/2025 at 10:51 PM
    Permalink

    На данном сайте вы можете приобрести онлайн телефонные номера различных операторов. Эти номера могут использоваться для регистрации аккаунтов в разных сервисах и приложениях.
    В каталоге доступны как постоянные, так и одноразовые номера, которые можно использовать для получения сообщений. Это удобное решение для тех, кто не хочет использовать личный номер в сети.
    аренда номера для регистрации
    Оформление заказа очень удобный: определяетесь с подходящий номер, вносите оплату, и он становится готов к использованию. Оцените услугу уже сегодня!

    Reply
  • 28/03/2025 at 4:11 PM
    Permalink

    Luxury timepieces have long been synonymous with precision. Crafted by renowned watchmakers, they seamlessly blend classic techniques with innovation.
    Each detail demonstrate exceptional quality, from precision-engineered calibers to high-end finishes.
    Wearing a timepiece is a true statement of status. It stands for sophisticated style and uncompromising quality.
    No matter if you love a minimalist aesthetic, Swiss watches deliver remarkable beauty that lasts for generations.
    http://www.tyrfing-rp.dk/forum/viewtopic.php?f=14&t=26765

    Reply
  • 30/03/2025 at 8:34 AM
    Permalink

    Darknet — это анонимная зона сети, доступ к которой только через защищенные браузеры, например, Tor.
    Здесь можно найти как легальные, но и нелегальные сайты, например, магазины и различные платформы.
    Одной из таких онлайн-площадок считается Блэк Спрут, данный ресурс специализировалась на продаже разных категорий, включая запрещенные продукты.
    https://bs2best
    Такие ресурсы довольно часто работают через анонимные платежи для обеспечения анонимности операций.
    Однако, спецслужбы время от времени закрывают крупные нелегальные рынки, однако вскоре появляются другие торговые точки.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *