Intro
Here I will not explain what is it SwiftUI, because if you already started reading this article, you definitely know what it is ?. I not, visit developer.apple.com to read more about SwiftUI. Also, I don’t want to explain how to create a new project (please don’t forget to check SwiftUI instead of Storyboard?)
In this article, I will show how I’ve built a simple app that does call request and fetching result on the List(TableView). We’ll use GitHub open-source API.
So Let’s dive straight into what we love to do — coding ???.
OTR, I love to use emoji ?
1. Building API Service ?
For this task, I’m using the Alamofire networking library to make my network requests. Also, for downloading and cashing images, was used AlamofireImage. It’s not the main topic of our article so you can check the code by the link in the open project at GitHub.?
2. ViewModel
Ok, we have got results. But how we can update our view?
Let’s create our ViewModel and it will conform to ObservableObject protocol. Then mark @Published
any properties you want to be observed by SwiftUI within this model. In this class, we create a property “repos” that marked with “@Published” property wrapper.
Also, we need to create an actual PassthroughSubject. PassthroughSubject is used to send values to subscribers when they become available.
And finally create and call ain Init() a function “fetchRepo”, where we call ”downloadTrendingRepos”.
Here I also created a property called “isLoading”. Its a boolean state of completion downloading repos. By default the state is true but when we’ve finished downloading, the state changes to false. I’ll use it in the future for showing my custom indicator.
import Combine
final class RepoViewModel: ObservableObject {
@Published var isLoading = true
@Published var repos = [Repo]() {
didSet {
didChange.send(self)
}
}
private let didChange = PassthroughSubject<RepoViewModel, Never>()
init() {
fetchRepo()
}
private func fetchRepo() {
DownloadService().downloadTrendingRepos { (reposResult) in
self.repos = reposResult
self.isLoading = false
}
}
}
view rawViewModel hosted with ❤ by GitHub
3. Main ContentView
Here, in fact, we begin our work with SwiftUI. For now, we’ve been added Text to our View “?HOT REPOS? “. We have to add at least one object ?♂️ because the compiler will show us an error. Next, create an instance of our view model: @ObservedObject var repoViewModel = RepoViewModel()
@ObservedObject will wrap your object into a dynamic view property. It allows SwiftUI to subscribe to your object. And if our model will changes, we can update our View.
import SwiftUI
struct ContentView: View {
@ObservedObject var repoViewModel = RepoViewModel()
var body: some View {
NavigationView{
if repoViewModel.isLoading {
Loader()
} else {
List(repoViewModel.repos){ model in
RepoCell(repoModel: model)
.shadow(color: .gray, radius: 4, x: 4, y: 4)
.padding(8.0)
}
.navigationBarTitle(Text("?HOT REPOS? "))
/// removing separators from the list
.onAppear(perform: {
UITableView.appearance().separatorStyle = .none
})
}
}
}
}
The next step is to make a design for each cell.
4. List “Cell”
struct RepoCell: View {
@State var repoModel: Repo
static var imagePlaceHolder = UIImage(named: "image_placeholder")
var body: some View {
Text("New Cell")
}
}
Create a new file “RepoCell”. This file is similar to UITableViewCell that created in UIKit. Also here we add to the variable “repoModel: Repo” which will hold our object.
At this stage, we will build the design of our cell. This process reminds me of the function “setupCell” that I’ve created in the UITableViewCell. What I like about the whistle is that I don’t need to create a separate xib and a Swift file. I can write everything in one View. Of course, we can split the code of our View into separate views. But for this project, we will leave everything in one file.
var body: some View {
// We set all elements in a Vertical Stack
VStack(alignment: .center, spacing: 0.0){
Text(repoModel.name)
.font(.headline)
.foregroundColor(Color.gitHubText) // Custom color
.padding()
Text(repoModel.description)
.font(.subheadline)
.fontWeight(.medium)
.foregroundColor(Color.gitHubText) // Custom color
.padding([.leading, .bottom, .trailing])
Image(uiImage: repoModel.image ?? RepoCell.imagePlaceHolder!)
.resizable()
.aspectRatio(contentMode: .fit)
.padding(.bottom)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 180)
HStack(alignment: .center) {
Group {
Image("numberOfForks")
.resizable()
.frame(width: 25, height: 25, alignment: .center)
Text(String(repoModel.numberOfForks))
.font(.system(size: 17))
.fontWeight(.regular)
.foregroundColor(Color.gitHubText) // Custom color
.multilineTextAlignment(.leading)
}
Divider()
.frame(width: 3)
.background(Color.gitHubText)
Group {
Image("language")
.resizable()
.frame(width: 25, height: 25, alignment: .center)
Text(repoModel.language)
.font(.system(size: 17))
.fontWeight(.regular)
.foregroundColor(Color.gitHubText) / Custom color
.multilineTextAlignment(.leading)
.frame(minWidth: 0, maxWidth: 50)
}
Divider()
.frame(width: 3)
.background(Color.gitHubText)
Group {
Image("contributers")
.resizable()
.frame(width: 25, height: 25, alignment: .center)
Text(String(repoModel.contributers))
.font(.system(size: 17))
.fontWeight(.regular)
.foregroundColor(Color.gitHubText)
.multilineTextAlignment(.leading)
.frame(minWidth: 0, maxWidth: 60)
}
}
.padding()
.frame(height: 40.0)
}
.padding()
.background(Color.white)
.cornerRadius(10)
.clipped()
}
}
Vertical Devider ?. It took me a while to figure out how to draw the vertical lines that in UIKit we made using with Views. In SwiftUI I’ve used “Devider()”. I’ve put him to HStack and gave a “.frame(width:3) .background(Color.gitHubText)”.
When I needed to pick a color, I could not find the variation of colors that were previously in UIKit. So I decided to create a custom color. Here I’ll show a few steps on how to create it.
Cool thing, right? You can use the preview in Xcode to see the result in “live mode”.
5. Put everything together
We are done with our cell and now we are returning to our main view.
var body: some View {
NavigationView{
List(reposArray, id: \.name){ model in
RepoCell(repoModel: model)
.shadow(color: .gray, radius: 4, x: 4, y: 4)
.padding(8.0)
}
.navigationBarTitle(Text("?HOT REPOS? "))
.onAppear(perform: loadData)
/// removing separators from the list
.onAppear(perform: {
UITableView.appearance().separatorStyle = .none
})
}
}
Follow Where we started We created a project in which there was a View and the text inside this view was a text. So first let’s wrap all our View with NavigationController?? In SwiftUi its called NavigationView. And this brings us for free all the animation and logic.
In the following order, add our TableView. Exactly, you’re right!? No more Table or CollectionView. Instead, we have a List. No more delegates and data source!?
Next, we just call our cell and pass our object. Add the rest of the design like shadow and padding. And voila, we’ve done! ? Easy peasy ?
Also, I really didn’t like the separators between the cells; I found a way to remove them – UITableView.appearance().separatorStyle = .none
I hope you enjoyed reading my article and found some useful info ??