Real-world News App using SwiftUI with API?handling

Real-world News App using SwiftUI with API?handling

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
Demo project gif


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:

No alt text provided for this image

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())
	    }
	}
No alt text provided for this image

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”.

No alt text provided for this image

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
No alt text provided for this image

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.

Lisa Dziuba

Head of Marketing | Forbes30u30 | ex Abstract, LottieFiles, WeLoveNoCode (acq. by Toptal), Lemon.io

5 年

thanks for sharing!

要查看或添加评论,请登录

Pramod Kumar的更多文章

社区洞察

其他会员也浏览了