Real-world News App using SwiftUI with API?handling
Pramod Kumar
Lead Consultant at HCL | Ex-Sapient, Appinventiv | Swift | iOS | Flutter | Android
In recent WWDC 2019, as Apple has introduces the SwiftUI, every one is so excited ?? about this.
So, in this tutorial we are going top discuss all about the SwiftUI. We’re going to make a real-world application for getting the News.
In this app, we’ll cover the following:
- Starting with the project in SwiftUI.
- Creating the UI with SwiftUI.
- Handle the REST API using URLSession and integrating with SwiftUI.
- Navigation from one screen to another.
- Managing the code structure with MVVM.
What is SwiftUI?
SwiftUI is a declarative Component-Oriented framework, a simple way to build user interfaces across all Apple platforms with the power of Swift.
?? Note: You need to upgrade you Xcode to 11.0 beta ??Demo
Create New Project
Let’s start by creating the new project, name it as “NewsApp”.
Don’t forget to check the “Use SwiftUI” option.
Follow the following steps for creating the application in the MVVM structure.
Note: Place an image with name “ic_news_placeholder” in “Assets.xcassets”
Create a model (M):
Create a new swift file, name it as “Article”. It’a a model that will hold the details of news.
let defaultText: String = "N/A"
struct Article: Hashable {
var sourceId: String = defaultText
var sourceName: String = defaultText
var author: String = defaultText
var title: String = defaultText
var description: String = defaultText
var url: String = defaultText
var urlToImage: String = defaultText
var publishedAt: String = defaultText
var content: String = defaultText
//"2019-06-07T10:37:36Z"
var publishDate: Date? {return self.publishedAt.toDate(dateFormat: "yyyy-MM-dd'T'HH:mm:ssZ")}
var imageUrl: URL? {return URL(string: urlToImage)}
init(json: JsonDictionary) {
if let source = json["source"] as? JsonDictionary {
if let obj = source["id"] {self.sourceId = "\(obj)"}
if let obj = source["name"] {self.sourceName = "\(obj)"}
}
if let obj = json["author"] {self.author = "\(obj)"}
if let obj = json["title"] {self.title = "\(obj)"}
if let obj = json["description"] {self.description = "\(obj)"}
if let obj = json["url"] {self.url = "\(obj)"}
if let obj = json["urlToImage"] {self.urlToImage = "\(obj)"}
if let obj = json["publishedAt"] {self.publishedAt = "\(obj)"}
if let obj = json["content"] {self.content = "\(obj)"}
}
static func getModels(json: [JsonDictionary]) -> [Artical] {return json.map { Artical(json: $0) }}
}
Our “Article” model have all the fields as per the “https://newsapi.org/v2/"
If we want to use our Article struct as the model which should be used by SwiftUI to build a View it has to conform either Hashable or Identifiable protocol. The only requirement of these protocol is id property, which has to be a Hashable value.
That used with ForEach loop in MainHomeV
Create view for row in List (V):
Now, we’ll start creating a view which represents Article row in list.
Click on File > New > File.
Select “SwiftUI View” > Click “Next”
Give the name as “NewsRowV”
Write the following code:
Here is the code for the NewsRowV:
import SwiftUI
struct NewsRowV : View {
let artical: Artical
var body: some View {
HStack {
Image("ic_news_placeholder")
.frame(width: 55.0, height: 41.0, alignment: Alignment.center)
.scaledToFit()
.clipped()
VStack(alignment: .leading) {
Text(artical.title)
.font(.headline)
Text(artical.description)
.font(.subheadline)
}
}
}
}
#if DEBUG
struct NewsRowV_Previews : PreviewProvider {
static var previews: some View {
NewsRowV(artical: Artical.getDefault())
}
}
Create Main Home with listing (V):
Create a new SwiftUI file, name it as “MainHomeV”.
import SwiftUI
struct MainHomeV : View {
@State private var searchQuery: String = "cricket"
@EnvironmentObject var viewModel: MainHomeVM
var todayStr: String {
Date().toString(dateFormat: "EEE, dd MMM yyyy")
}
var body: some View {
NavigationView {
List {
SearchBarV(text: $searchQuery, placeholder: Text("Search"), onCommit: search)
ForEach(viewModel.articals.identified(by: \.self)) { artcl in
NavigationButton(
destination: NewsDetailV(artical: artcl)) {
NewsRowV(artical: artcl)
}
}
}.navigationBarTitle(Text(todayStr))
}.onAppear(perform: search)
}
private func search() {
viewModel.search(forQuery: searchQuery)
}
}
#if DEBUG
struct MainHomeV_Previews : PreviewProvider {
static var previews: some View {
MainHomeV()
}
}
We’re creating a List that will be holding the “NewsRowV” objects.
searchQuery that is marked as @State, means that this view is derived from this state. As soon as this state will change, SwiftUI rebuilds this view to reflect the changes.
$searchQuery, It means to get a reference for property wrapper, not value itself. We use it to connect TextField and query variable in two way binding.
Other interesting fact is @EnvironmentObject. It is a part of feature called Environment. We can populate our Environment with all needed service classes and then access them from any view inside that Environment. The Environment is the right way of Dependency Injection with SwiftUI.
Create a ViewModel for main home view (VM):
Now, we need to create a view model for the main home view, hold all the data that will be shown on the home view.
We’ll be make the REST API request in this view model with the help of the URLSession.
import SwiftUI
import Combine
class MainHomeVM: BindableObject {
var articals: [Artical] = [] {
didSet {
didChange.send(self)
}
}
var didChange = PassthroughSubject<MainHomeVM, Never>()
func search(forQuery searchQuery: String) {
var param = JsonDictionary()
param["q"] = searchQuery
let todayStr = Date().add(days: -2)?.toString(dateFormat: "yyyy-MM-dd") ?? ""
if !todayStr.isEmpty {
param["from"] = todayStr
}
param["sortBy"] = "publishedAt"
param["apiKey"] = AppConstants.apiKey
APICaller.shared.callGetAllNewsAPI(param: param) { [weak self](success, errorMsg, artcl) in
if success {
self?.articals = artcl
}
else {
self?.articals = []
}
}
}
}
MainHomeVM class should conform BindableObject protocol, which requires a didChange property. It makes possible to use it inside Environment and rebuild view as soon as it changes. The didChange property should be a Publisher, which is a part of a new Apple’s Reactive framework called Combine. The main goal of Publisher is to notify all subscribers when something changes. That’s why in didSet of our repos array we tell to our subscribers that data changed. As soon as new values appear, SwiftUI will rebuild MainHomeV.
APICaller is a wrapper that is written over the PKNetworking.
Create detail screen for news:
Let’s create another SwiftUI file to show the NewsDetails, name it as “NewsDetailV”.
Here is the code for the detail view:
import SwiftUI
struct NewsDetailV : View {
let artical: Artical
private let imageWidth: CGFloat = (UIDevice.screenWidth-30.0)
var body: some View {
VStack(alignment: .center) {
Image("ic_news_placeholder")
.frame(width: imageWidth, height: imageWidth * 0.5, alignment: Alignment.center)
.scaledToFill()
Text(verbatim: artical.title)
.lineLimit(nil)
.font(.headline)
HStack {
Image(uiImage: #imageLiteral(resourceName: "ic_publish"))
.frame(width: 20.0, height: 20.0, alignment: Alignment.center)
Text(artical.publishDate?.toString(dateFormat: "d MMM, yyyy") ?? defaultText)
.font(.system(size: 12.0))
Spacer()
Image(uiImage: #imageLiteral(resourceName: "ic_author"))
.frame(width: 20.0, height: 20.0, alignment: Alignment.center)
Text(artical.sourceName)
.font(.system(size: 12.0))
}
.padding(EdgeInsets(top: 0.0, leading: 16.0, bottom: 0.0, trailing: 16.0))
Text(artical.description)
.lineLimit(nil)
.font(.subheadline)
.padding(.top, 16.0)
}
.padding(EdgeInsets(top: 0.0, leading: 16.0, bottom: 0.0, trailing: 16.0))
.offset(x: 0, y: -180)
.padding(.bottom, -180)
}
}
#if DEBUG
struct NewsDetailV_Previews : PreviewProvider {
static var previews: some View {
NewsDetailV(artical: Artical.getDefault())
}
}
#endif
Conclusion
And this is how we can develop our new application with the SwiftUI.
After playing with the SwiftUI more than a week, I really enjoying creating new UI using this new framework. Using, SwiftUI makes UI development very easy, fast and allow us to write very less code.
I’m still exploring the new things in SwiftUI, so please let me know if you found anything new or any error in this tutorial.
Visit this link to download the complete project.
????? !!! HAPPY CODING !!! ?????
Thank you for reading, please hit the recommend icon if like this collection ?? . Questions? Leave them in the comment.
Head of Marketing | Forbes30u30 | ex Abstract, LottieFiles, WeLoveNoCode (acq. by Toptal), Lemon.io
5 年thanks for sharing!