0
\$\begingroup\$

So basically I am building an app that helps to download some documents via a rest api.

Here is the SearchViewController

import UIKit

class SearchViewController: UIViewController, UISearchResultsUpdating, UISearchBarDelegate {

    let searchController = UISearchController(searchResultsController: nil)

    @IBOutlet weak var searchTableView: UITableView!
    @IBOutlet weak var activityIndicator: UIActivityIndicatorView!
    @IBOutlet weak var loadingTextLabel: UILabel!
    @IBOutlet weak var retryButton: UIButton!

    @IBAction func retryButtonTapped(_ sender: Any) {
    }
    var papers: [Paper] = []
    var filteredPapers: [Paper] = []


    // MARK: Search controlls
    func filterContentForSearchText(searchText: String, scope: String = "All") {
        print(searchText)

        self.filteredPapers = papers.filter {
            paper in
            return paper.name!.lowercased().contains(searchText.lowercased())
        }

        self.searchTableView.reloadData()

    }

    func updateSearchResults(for searchController: UISearchController) {
        let searchBar = searchController.searchBar
        let scope = searchBar.scopeButtonTitles![searchBar.selectedScopeButtonIndex]
        self.filterContentForSearchText(searchText: searchController.searchBar.text!, scope: scope)
    }

    func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
        self.filterContentForSearchText(searchText: searchBar.text!, scope: searchBar.scopeButtonTitles![selectedScope])
    }

    func showLoading() {
        self.activityIndicator.startAnimating()
        self.activityIndicator.isHidden = false
        self.loadingTextLabel.isHidden = false
        self.loadingTextLabel.text = "Moving satalites into position..."
        self.searchTableView.isHidden = true
        self.retryButton.isHidden = true
    }

    func showTable() {
        self.activityIndicator.stopAnimating()
        self.activityIndicator.isHidden = true
        self.loadingTextLabel.isHidden = true
        self.searchTableView.isHidden = false
        self.retryButton.isHidden = true
    }

    func showRetry() {
        self.activityIndicator.stopAnimating()
        self.activityIndicator.isHidden = true
        self.searchTableView.isHidden = true
        self.loadingTextLabel.text = "Some error occured!"
        self.loadingTextLabel.isHidden = false
        self.retryButton.isHidden = false
    }

    func loadData() {
        let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext

        do {
            self.papers = try context.fetch(Paper.createFetchRequest())
            self.searchTableView.reloadData()
        }
        catch {
            print("Fetching failed")
        }

    }


    override func viewDidLoad() {
        super.viewDidLoad()

        self.searchTableView.dataSource = self
        self.searchTableView.delegate = self

        self.searchController.searchResultsUpdater = self
        self.searchController.dimsBackgroundDuringPresentation = false
        self.definesPresentationContext = true
        self.searchTableView.tableHeaderView = searchController.searchBar
        searchController.searchBar.scopeButtonTitles = ["All", "ST1", "ST2", "PUT", "UT"]
        searchController.searchBar.delegate = self


        APIManager.sharedInstance.delegate = self
        APIManager.sharedInstance.getLastUpdate()

    }

    override func viewDidAppear(_ animated: Bool) {
        let launchedBefore = UserDefaults.standard.bool(forKey: "launchedBefore")

        if !launchedBefore {

            self.showLoading()

            // Call get all papers endpoint and populate db
            APIManager.sharedInstance.getAllPapers()

        }
        else {
            self.showTable()
            self.loadData()

        }
    }
}


// MARK: Table View datasource
extension SearchViewController: UITableViewDataSource {

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if self.searchController.isActive && self.searchController.searchBar.text != "" {
            return filteredPapers.count
        }
        return self.papers.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = self.searchTableView.dequeueReusableCell(withIdentifier: "search-cell") as! SearchCell
        let paper: Paper
        if searchController.isActive && searchController.searchBar.text != "" {
            paper = self.filteredPapers[indexPath.row]
            print(paper)
        } else {
            paper = self.papers[indexPath.row]
        }
        cell.nameLabel.text = paper.name
        cell.examTypeLabel.text = paper.examTypeID?.stringValue ?? "Not available"
        return cell
    }

}

// MARK: Table View delegate
extension SearchViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
        let downloadButton = UITableViewRowAction(style: .normal, title: "Download") {
            action, index in

            let paper = self.papers[indexPath.row]
            let url = paper.fileURL

            // Call the download endpoint
            APIManager.sharedInstance.downloadPaper(url: url!)

            self.searchTableView.isEditing = false
        }

        downloadButton.backgroundColor = UIColor.lightGray

        return [downloadButton]
    }

    func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
        return true
    }
}

extension SearchViewController: APIManagerDelegate {

    func didFinishDownloadAll(success: Bool) {
        if success {
            // First launch is considered only when the data is successfully saved for the first time
            UserDefaults.standard.set(true, forKey: "launchedBefore")
            self.loadData()
            self.showTable()
        }
        else {
            self.showRetry()
        }
    }

    func didFinishDownload(success: Bool) {
        if success {
            self.tabBarController?.tabBar.items?.last?.badgeValue = "1"
        }
        else {
            let alert = UIAlertController(title: "Download Failed!", message: "The download has failed due to some unexpected reason. Please check your network.", preferredStyle: UIAlertControllerStyle.alert)
            alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
            self.present(alert, animated: true, completion: nil)
        }


    }

    func didFinishUpdate(success: Bool) {
        self.loadData()
    }


}

Here is the APIManager

import UIKit
import Alamofire
import SwiftyJSON

protocol APIManagerDelegate: class {
    func didFinishDownloadAll(success: Bool)
    func didFinishDownload(success: Bool)
    func didFinishUpdate(success: Bool)
}


class APIManager {
    static let sharedInstance = APIManager()
    weak var delegate: APIManagerDelegate?

    func getAllPapers() {
        UIApplication.shared.isNetworkActivityIndicatorVisible = true
        Alamofire.request(Router.getAll()).responseJSON {
            response in

            UIApplication.shared.isNetworkActivityIndicatorVisible = false

            if response.result.isSuccess == true {
                let delegate = UIApplication.shared.delegate as! AppDelegate
                let context = delegate.persistentContainer.viewContext
                if let data = response.result.value {
                    let json = JSON(data)
                    for item in json {
                        let paper = Paper(context: context)
                        print(item)

                        paper.fileURL =  item.1.dictionaryValue["file_url"]?.stringValue
                        paper.semester = item.1.dictionaryValue["semester"]?.numberValue
                        paper.examTypeID = item.1.dictionaryValue["exam_type_id"]?.numberValue
                        paper.name = paper.fileURL?.components(separatedBy: "/").last?.components(separatedBy: ".").dropLast().joined()
                        delegate.saveContext()
                    }
                }
                self.delegate?.didFinishDownloadAll(success: true)
            } else {

                print("kuch to hua hai")
                self.delegate?.didFinishDownloadAll(success: false)
            }
        }
    }

    func getVersion() {
        UIApplication.shared.isNetworkActivityIndicatorVisible = true
        Alamofire.request(Router.getVersion()).responseString {
            response in
            UIApplication.shared.isNetworkActivityIndicatorVisible = false
            if let recievedString = response.result.value {
                print(recievedString)
            }
        }
    }

    func getLastUpdate() {
        UIApplication.shared.isNetworkActivityIndicatorVisible = true
        Alamofire.request(Router.getLastUpdate()).responseJSON{
            response in
            UIApplication.shared.isNetworkActivityIndicatorVisible = false
            if let data = response.result.value {
                let json = JSON(data)
                if let dateString: String = json.stringValue.components(separatedBy: ".").first {
                    let dateFormatter: DateFormatter = DateFormatter()
                    dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
                    if let date = dateFormatter.date(from: dateString) {
                        print(date)
                    }

                    self.delegate?.didFinishUpdate(success: true)

                }
            }
            else {
                print("error")
                self.delegate?.didFinishUpdate(success: false)

            }
        }
    }

    func downloadPaper(url: String) {
        UIApplication.shared.isNetworkActivityIndicatorVisible = true
        let destination = DownloadRequest.suggestedDownloadDestination(for: .documentDirectory, in: .userDomainMask)
//        let fileURL = URL(string: url.replacingOccurrences(of: " ", with: "%20"))
        let fileURL = URL(string: "http://placehold.it/600/f66b97")

        Alamofire.download(fileURL!, to: destination).response {
            response in
            UIApplication.shared.isNetworkActivityIndicatorVisible = false
            if response.error == nil{
                print("Downloaded file successfully")

                self.delegate?.didFinishDownload(success: true)
            }
            else{
                print("Failed with error: \(response.error)")
                self.delegate?.didFinishDownload(success: false)
            }
        }



    }

}

Paper+CoreDataProperties is as follows

import Foundation
import CoreData


extension Paper {

    @nonobjc public class func createFetchRequest() -> NSFetchRequest<Paper> {
        return NSFetchRequest<Paper>(entityName: "Paper");
    }

    @NSManaged public var examTypeID: NSNumber?
    @NSManaged public var fileURL: String?
    @NSManaged public var semester: NSNumber?
    @NSManaged public var name: String?

}

And last is Paper+CoreDataClass

import Foundation
import CoreData

@objc(Paper)
public class Paper: NSManagedObject {

}

Should the saving of data be done in the APIManager class? Or is there a better way to do this? Also this is the first time I have written a delegate, so is this the correct way to do this?

\$\endgroup\$
1
\$\begingroup\$
let delegate = UIApplication.shared.delegate as! AppDelegate

While Xcode puts core data boilerplate into the AppDelegate for you, using the AppDelegate for Core Data, or for anything else other than the app's handling of launching / backgrounding ect. isn't a good habit to get into IMO.

Putting a network call into viewDidAppear is also not a good practice. It looks like you want to restrict that call using user Defaults to stash a bool, but that can be accomplished by just making a network call for data in viewDidload. Since there doesn't appear to be a case where launchedBefore ever get's set to false, the network call you're trying to gate off will never be called again after the first time this app runs. User defaults whose states never change are a code smell.

the delegate as you have it appears to be hooked up correctly, though I haven't run this code.

Using delegates with a singleton object can get complicated, as per https://stackoverflow.com/questions/9475469/delegate-for-a-singleton-object , I would use completion handlers (closures).

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.