Swift Repos
Overview
- 
In this lab, we will build an application to look at the most popular Swift repisitories on GitHub using the GitHub API and many of the skills we have learned in class. You are allowed and encouraged to use Alamofire and Codable to make it easier to write this lab and to produce cleaner code. The final application will look like this:   
Setup
- 
Clone the starter code.  When you open the project in Xcode and go to Models/Parser.swiftyou see an error because Xcode does not know whatimport Alamofiremeans.
 
- 
To solve this problem, first go to Swift Package Index at https://swiftpackageindex.com/ and look up 'Alamofire'.  Click on the 'Use this Package' button and copy the Package URL. (If you want to see the Alamofire repo and read more about it, you can click on the 'View on GitHub' link on the right.) 
- 
Now to install this dependency in Xcode, we will use Swift Package Manager as it is already baked into Xcode. (The alternative that predates SPM is a Ruby gem called CocoaPods and you will still see it in widespread use and it will be demo'd in class.)  To do this: 
- 
in Xcode, go to the menu option Fileand select theAdd Packages...option
 
- 
In the modal that pops up, on the far right there is a place to search or enter a package URL.  Paste in the link you got in the prior step and add the package. 
- 
Once this completes, you are now ready to use Alamofire.  You can verify that by seeing the previous error Models/Parser.swiftdisappears (and a new one appears).
 
 
Models
- 
To fix the new error, we need to fix our model in Repository.swift. Take note of the simple structure of the model.
 
- 
Here is the JSON we are going to parse. Look at it and take note of its structure, look at the fields you are going to need. Now make this struct conform to both the CodableandIdentifiableprotocols.  Remember that forCodableyou will need to set upCodingKeys. (Refer to the earlier playground from class, but note we made a small change so you can't just copy and paste.)
 
- 
Now we need to make a parent struct to hold all these Repositoryobjects.  In class we called thatResultand decoded some other aspects, but now we only care about a having a container to hold our repos, so we will create a new struct calledRepositoriesas follows:
 struct Repositories: Codable {
 let items: [Repository]
 // make sure this conforms to Codable...
}
- 
Open up the Parser.swiftand look over the code there.  Because the network calls via Alamofire is not the main focus of this lab, to save time we are simply giving you a fully working parser.  Note that unlike class, we didn't separate this into two classes (one for parsing, the other for the actual retrieval of data) because this is so simple, but that is normally a good idea.  We aren't necessarily following best practice of splitting out the params because there no opportunity to change them in this simple app; if you want to do this on your own and follow the class example, we certainly encourage you to do so.
 I realize most students want to just get through lab as quickly as possible, but I would urge you to take a few minutes to look over the Alamofire documentation and learn more how this nifty tool works -- you will certainly need it more when it comes to project time. 
View Model
- 
Open up the ViewModel.Swift.
 
- 
We want changes in our view model to be automatically read by our interface so it can update itself.  For this reason, our view model already conforms to the ObservableObjectprotocol.
 
- 
Now to be an ObservableObject, we must have some values with are being 'published' so that other observers can subscribe to and see changes.  I promise we will explain the concept of reactive programming more in lecture soon, but for now we've given you the stubs for three fields that need to be publishable; mark them as published by putting@Publishedin the front of each and set reasonable initial values with the right data types.
 
- 
repos: An array of all theRepositoryobjects
- 
searchText: The search bar text as aString
- 
filteredRepos: An array of the filteredRepositoryobjects
 
- 
Add the following method that will filter the repos:   func search(searchText: String) {
    self.filteredRepos = self.repos.filter { repo in
      return repo.name.lowercased().contains(searchText.lowercased())
    }
  }
Views
List Row Cell (RepositoryRow)
Let's start easy with the SwiftUI file called RepositoryRow.swift that basically holds the contents of an individual table cell.  Recall from lecture that a cell displays the repository's name (let's make that a .headline font) and itemDescription (a .subheadline font) and these are placed with the name on top and the description below. Note that the struct will need to have a Repository that it can reference for these values.
Hopefully the code you developed in the class exercise on Tuesday will be helpful to you in that regard.  If you are still having issues with this, here is an excellent resource that will also be helpful for creating the List View.
List View (ContentView)
- 
Now time to hit the challenging one: our list of repos in ContentView.swift.  There are fields that we'd like to have right at the top of the struct before we even go intovar bodyto help us manage the state of our app:
 var searchField: String = ""
var displayedRepos = [Repository]()
 As we add things to the search string, our displayed repos should update accordingly.  Oh no.  Houston, we have a problem.  This is a struct (as all SwiftUI views are), not a class, so the state should be immutable and we can't update these values.  To rectify this, we will make them property wrappers by adding @Statein front of each.  We are now handing over these over to SwiftUI so that it remains persistent in memory as long as the view exists and when the state changes, SwiftUI will automatically reload with the latest changes.
 
- 
Next we have to get a source of truth for the view, to actually know information from the view model, like those published fields we have there.  Right after our @Statevariables, we need to create an instance of theViewModeland since the view model is anObservableObjectthen this instance would be an@ObservedObject(place that in front, similar to what we did with@Stateabove).
 
- 
To update the repos responsively (as the user types), we are going to need to create a custom binding for the searchFieldfrom the search bar:
   let binding = Binding<String>(get: {
    self.searchField
  }, set: {
    self.searchField = $0
    self.viewModel.search(searchText: self.searchField)
    self.displayRepos()
  })
This goes immediately inside var body: some View.
 
- 
Now this will display an error because we haven't defined a function called displayRepos().  To correct that, go outside thevar body: some View {}block (but still in the struct) and add the following code:
 func loadData() {
  Parser().fetchRepositories { (repos) in
    self.viewModel.repos = repos
    self.displayedRepos = repos
  }
}
func displayRepos() {
  if searchField == "" {
    displayedRepos = viewModel.repos
  } else {
    displayedRepos = viewModel.filteredRepos
  }
}
As a bonus, this will give us a method to load data as well. 
- 
Now we get to creating the view itself.  Within the var body: some View {}block (but after ourbinding, go ahead and return a VStack; the first thing it will have is aTextFieldand in the placeholdertext: Valuereplace the value withbinding.
 
- 
The next thing in our VStack is a Listthat is populated withRepositoryRowviews as we did in class with PriceCheck.  This list is based off ofdisplayedRepos, which can change as the search is activated.  Finally, add the.navigationBarTitleto theListelement so it says "Swift Repos".
 
- 
For the entire VStack, add on .onAppear(perform: loadData). This is updating thedisplayedReposwhen the View appears.
 
- 
Run the app in a simulator and see that the app displays the repos from the API call and that searching filters out that list. 
WebView (RepositoryDetail)
This view is given to you. Look it over to see how it works. Now here's the trick: we want to make sure each row in the list (in the ContentView) links to the appropriate GitHub repo. (hint: use a NavigationLink like we did in class with the destination being WebView(request: URLRequest(url:URL(string: repository.htmlURL)!).)
If time allows, you can also explore customizing the views more and cleaning up some of the code.  Feel free to explore further--that's where the real fun and learning is!
Qapla'