I'm trying to populate my table view with the return of my HealthKit from Apple Health. I currently get the date and heart rate from Apple Health from the past 7 days. I can see everything clearly in my console.
Here's my block code:
func getTodaysHeartRate(completion: (#escaping (HKQuantitySample) -> Void)) {
print("func")
let heartRateUnit:HKUnit = HKUnit(from: "count/min")
let heartRateType:HKQuantityType = HKQuantityType.quantityType(forIdentifier: .heartRate)!
let startDate = Date() - 7 * 24 * 60 * 60
let endDate = Date()
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: [])
let sortDescriptors = [
NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)
]
let heartRateQuery = HKSampleQuery(sampleType: heartRateType,
predicate: predicate,
limit: Int(HKObjectQueryNoLimit),
sortDescriptors: sortDescriptors)
{ (query:HKSampleQuery, results:[HKSample]?, error:Error?) -> Void in
guard error == nil else { print("error"); return }
//print("results")
//print(results!)
for result in results! {
guard let currData:HKQuantitySample = result as? HKQuantitySample else { return }
print("Heart Rate: \(currData.quantity.doubleValue(for: heartRateUnit))")
print("Times: \(currData.startDate)")
//print("End Date: \(currData.endDate)")
//print("quantityType: \(currData.quantityType)")
//print("Metadata: \(String(describing: currData.metadata))")
//print("UUID: \(currData.uuid)")
//print("Source: \(currData.sourceRevision)")
//print("Device: \(String(describing: currData.device))")
//print("---------------------------------\n")
}
}
healthStore.execute(heartRateQuery)
}
#IBOutlet weak var heartRate: UILabel!
#IBAction func getHeartRate(_ sender: Any) {
getTodaysHeartRate { (result) in
print("\(result)")
DispatchQueue.main.async {
self.heartRate.text = "\(result)"
}
}
}
#IBAction func emailResult(_ sender: Any) {
self.sendEmail()
}
Here I have created as sample project for you where I made some changes with your code which is shown below and comments are added for that:
import UIKit
import HealthKit
class ViewController: UIViewController {
//add a tableview to your storyboard and connect delegate and datasource
#IBOutlet weak var tblHeartRateData: UITableView!
let healthStore = HKHealthStore()
//create an array of HKSample which will hold your data from healthkit or you can create a custom class for that
var heartRateData: [HKSample]?
override func viewDidLoad() {
super.viewDidLoad()
//Get data access permission
getHealthKitPermission()
}
#IBAction func getHeartRate(_ sender: Any) {
getTodaysHeartRate { (result) in
DispatchQueue.main.async {
//Here you will get your healthkit data.
self.heartRateData = result
//Once you get it reload your table view
self.tblHeartRateData.reloadData()
}
}
}
//Permission
func getHealthKitPermission() {
let healthkitTypesToRead = NSSet(array: [
HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate) ?? ""
])
healthStore.requestAuthorization(toShare: nil, read: healthkitTypesToRead as? Set) { (success, error) in
if success {
print("Permission accept.")
} else {
if error != nil {
print(error ?? "")
}
print("Permission denied.")
}
}
}
func getTodaysHeartRate(completion: (#escaping ([HKSample]) -> Void)) {
print("func")
let heartRateType:HKQuantityType = HKQuantityType.quantityType(forIdentifier: .heartRate)!
//predicate
let startDate = Date() - 2 * 24 * 60 * 60
let endDate = Date()
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: [])
//descriptor
let sortDescriptors = [
NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)
]
let heartRateQuery = HKSampleQuery(sampleType: heartRateType,
predicate: predicate,
limit: Int(HKObjectQueryNoLimit),
sortDescriptors: sortDescriptors)
{ (query:HKSampleQuery, results:[HKSample]?, error:Error?) -> Void in
guard error == nil else { print("error"); return }
//Here I have added completion which will pass data when button will tap.
completion(results!)
}
healthStore.execute(heartRateQuery)
}
}
//TableView Methods.
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return heartRateData?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! HeartDataTableViewCell
let heartRateUnit:HKUnit = HKUnit(from: "count/min")
let heartData = self.heartRateData?[indexPath.row]
guard let currData:HKQuantitySample = heartData as? HKQuantitySample else { return UITableViewCell()}
cell.lblHeartRate.text = "Heart Rate: \(currData.quantity.doubleValue(for: heartRateUnit))"
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 106
}
}
for more info check our THIS demo project.
And your result will be:
Related
Sorry guys if there's already a thread about it but been stuck with it for a while.
I'm kinda new to CoreData(knows only how to persist and fetch items only) and I'm trying to do a little query in my app which will load only the ones with the isDone attribute = to "True".
the thing is I don't know how to use NSFetchRequest & NSPredicate so im kinda stuck, hope you guys can help me out with some tips <3, Here's my code:
import Foundation
import CoreData
import UIKit
import SwipeCellKit
class TasksManViewController: UITableViewController, SwipeTableViewCellDelegate {
#IBOutlet weak var Sege: UISegmentedControl!
let isSwipeRightEnabled = true
var tasksArray = [Task](){
didSet {
// because we perform this operation on the main thread, it is safe
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
var doneTasksArr = [Task]() // an array of what to disply.
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
override func viewDidLoad() {
}
override func viewWillAppear(_ animated: Bool) {
loadTasks()
}
// MARK: - DataSource + Delegate Methods:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tasksArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "taskCellRow") as! SwipeTableViewCell
cell.delegate = self
cell.textLabel?.text = tasksArray[indexPath.row].title
return cell
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]? {
if orientation == .left {
guard isSwipeRightEnabled else { return nil }
let doneAction = SwipeAction(style: .destructive, title: "Done") { (action, indexPath) in
//STEP1: Append the task to the doneTasksArr:
self.tasksArray[indexPath.row].isDone = true
self.doneTasksArr.append(self.tasksArray[indexPath.row])
//STEP2: Delete the task from the tasksArray since it was done.
self.context.delete(self.tasksArray[indexPath.row])
//STEP3: Remove the Row:
self.tasksArray.remove(at: indexPath.row)
//STEP4: Update the Model:
self.saveTasks()
self.tableView.reloadData()
}
//configure btn:
doneAction.backgroundColor = .cyan
return [doneAction]
} else {
let deleteAction = SwipeAction(style: .destructive, title: "Delete") { action, indexPath in
self.context.delete(self.tasksArray[indexPath.row])
self.tasksArray.remove(at: indexPath.row)
self.saveTasks()
self.tableView.reloadData()
}
return [deleteAction]
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
// MARK: - Class Methods:
#IBAction func addBtnTapped(_ sender: UIBarButtonItem) {
insertNewTask()
}
func insertNewTask() {
var textField = UITextField()
let alert = UIAlertController(title: "New Task", message: "Please Add Your Task", preferredStyle: .alert)
alert.addTextField { (alertTextField) in
alertTextField.placeholder = "Create New Task"
textField = alertTextField
}
let action = UIAlertAction(title: "Add", style: .default) { (action) in
let newItem = Task(context: self.context)
newItem.title = textField.text!
newItem.isDone = false
self.tasksArray.append(newItem)
self.saveTasks()
self.tableView.reloadData()
}
alert.addAction(action)
self.present(alert, animated: true, completion: nil)
}
// MARK: - Sege Section:
#IBAction func segeControlTapped(_ sender: UISegmentedControl) {
switch Sege.selectedSegmentIndex
{
case 0:
//Loading normal tasks which not done
loadTasks()
case 1:
//Loading the doneTasks:
loadDoneTasksFrom()
default:
print("There's something wrong with Sege!")
}
tableView.reloadData()
}
//MARK: - Model Manipulation Methods:
func saveTasks() {
do {
try! context.save()
} catch {
print("Error Saving context \(error)")
}
}
func loadTasks() {
let request: NSFetchRequest<Task> = Task.fetchRequest()
do{
tasksArray = try! context.fetch(request)
} catch {
print("There was an error with loading items \(error)")
}
}
func loadDoneTasksFrom() {
let request: NSFetchRequest<Task> = Task.fetchRequest()
request.predicate = NSPredicate(format: "isDone == %#", "true")
request.sortDescriptors = [NSSortDescriptor(key: "isDone", ascending: false)]
do{
tasksArray = try context.fetch(request)
} catch {
print("Error fetching data from context\(error)")
}
tableView.reloadData()
}
}
You can use one of these
NSPredicate(format: "isDone == %#", NSNumber(value: true))
NSPredicate(format: "isDone = %d", true)
You can write queries like this
static func getDoneTasks() -> NSFetchRequest<Task> {
let request:NSFetchRequest<Task> = Task.fetchRequest() as! NSFetchRequest<Task>
let sortDescriptor = NSSortDescriptor(key: "createdAt", ascending: false)
request.sortDescriptors = [sortDescriptor]
let isDone = true
request.predicate = NSPredicate(format: "isDone == %#", isDone)
return request
}
And then you just fetch them with:
#FetchRequest(fetchRequest: Task.getDoneTasks()) var doneTasks: FetchedResults<Task>
You also can add arguments etc. to your function and pass them in the FetchRequest
I can recommend this tutorial to understand the core concepts of coredata
Yay fellas! I solved it, I splited it to two functions like this: thank you very much for your help ! <3 I've learned how to use CoreData :) much love!.
func loadTasks() {
let request: NSFetchRequest<Task> = Task.fetchRequest()
request.predicate = NSPredicate(format: "isDone == %#", NSNumber(value: false))
request.sortDescriptors = [NSSortDescriptor(key: "isDone", ascending: false)]
do{
tasksArray = try! context.fetch(request)
} catch {
print("There was an error with loading items \(error)")
}
tableView.reloadData()
}
func loadDoneTasksFrom() {
let request:NSFetchRequest<Task> = Task.fetchRequest()
request.predicate = NSPredicate(format: "isDone == %#", NSNumber(value: true))
request.sortDescriptors = [NSSortDescriptor(key: "isDone", ascending: false)]
do{
tasksArray = try context.fetch(request)
} catch {
print("Error fetching data from context\(error)")
}
tableView.reloadData()
}
and then in the Sege for case 0(not done task) I loadTasks, and in case1 I load DoneTasks.
I am trying to implement a search function in my app. For now, I'm just trying to search by the State value my JSON, though I'd like to eventually include Category as well. There are 9 rows total, the first 7 are State=AZ and the last 2 are State=CA. When I search for "KK" the table is empty, which makes sense. But when I search for "CA" I get two rows like I expect, but they are the first two rows in the JSON, which are both AZ, not the two CA rows it should be.
I suspect my issue is somewhere in my filterContentForSearchText function, but since I'm not sure exactly which code you need, here is the ViewController (the function I think is the issue is down near the end):
import UIKit
import os.log
import Foundation
class BonusListViewController: UITableViewController {
var bonuses = [JsonFile.JsonBonuses]()
var filteredBonuses = [JsonFile.JsonBonuses]()
var detailViewController: BonusDetailViewController? = nil
let defaults = UserDefaults.standard
let searchController = UISearchController(searchResultsController: nil)
override func viewDidLoad() {
super.viewDidLoad()
// MARK: Search Support
searchController.searchResultsUpdater = self
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "Enter two letter state to filter"
navigationItem.searchController = searchController
definesPresentationContext = true
// MARK: Settings Data Struct
struct Constants {
struct RiderData {
let riderNumToH = "riderNumToH"
let pillionNumToH = "pillionNumToH"
}
struct RallyData {
let emailDestinationToH = "emailDestinationToH"
}
}
//MARK: Load the bonuses
loadBonuses { [weak self] bonuses in
self?.bonuses = bonuses ?? []
DispatchQueue.main.async {
self?.tableView.reloadData()
}
print("loadBonuses called")
}
}
// MARK: - Table View Configuration
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isFiltering() {
print("Showing \(filteredBonuses.count) Filtered Results")
return filteredBonuses.count
}
print("Found \(bonuses.count) rows in section.")
return bonuses.count
}
/* Disabling the swipe function until I code it to actually do something
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration?
{
let clearAction = UIContextualAction(style: .normal, title: "Clear Data") { (contextAction: UIContextualAction, sourceView: UIView, completionHandler: (Bool) -> Void) in
print("Clear Action Tapped")
completionHandler(true)
}
clearAction.backgroundColor = .blue
let swipeConfig = UISwipeActionsConfiguration(actions: [clearAction])
return swipeConfig
}
*/
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "BonusListViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? BonusListViewCell else {
fatalError("The dequeued cell is not an instance of BonusListViewCell.")
}
let bonus = bonuses[indexPath.row]
let bonusSet: JsonFile.JsonBonuses
if isFiltering() {
bonusSet = filteredBonuses[indexPath.row]
} else {
bonusSet = bonus
}
let urlString = "http://tourofhonor.com/appimages/"+(bonus.imageName)
let url = URL(string: urlString)
cell.primaryImage.downloadedFrom(url: url!)
cell.nameLabel.text = bonus.name.capitalized
cell.bonusCodeLabel.text = bonus.bonusCode.localizedUppercase
cell.categoryLabel.text = bonus.category
cell.valueLabel.text = "\(bonus.value)"
cell.cityLabel.text = "\(bonus.city.capitalized),"
cell.stateLabel.text = bonus.state.localizedUppercase
return cell
}
// MARK: Functions
// MARK: - Fetch JSON from ToH webserver
func downloadJSON(completed: #escaping ([JsonFile.JsonBonuses]?) -> ()) {
let url = URL(string: "http://tourofhonor.com/BonusData.json")!
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error == nil, let data = data {
do {
let posts = try JSONDecoder().decode(JsonFile.self, from: data)
completed(posts.bonuses)
} catch {
print("JSON Download Failed")
}
} else {
print("downloadJSON completed")
completed(nil)
}
}.resume()
}
func saveBonuses(_ bonuses: [JsonFile.JsonBonuses], to url: URL) {
try? FileManager.default.removeItem(at: url)
do {
let data = try JSONEncoder().encode(bonuses)
try data.write(to: url)
print("saveBonuses successful")
} catch {
print("Error saving bonuses to file:", error)
}
}
func loadBonusesFromFile(_ url: URL) -> [JsonFile.JsonBonuses]? {
do {
let data = try Data(contentsOf: url)
let bonuses = try JSONDecoder().decode([JsonFile.JsonBonuses].self, from: data)
print("loadBonusesFromFile successful")
return bonuses
} catch {
print("Error loading bonuses from file:", error)
return nil
}
}
func loadBonuses(completion: #escaping ([JsonFile.JsonBonuses]?) -> Void) {
let localBonusesURL = try! FileManager.default
.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent("Bonuses.json")
downloadJSON { bonuses in
if let bonuses = bonuses {
completion(bonuses)
self.saveBonuses(bonuses, to: localBonusesURL)
} else {
completion(self.loadBonusesFromFile(localBonusesURL))
}
}
}
func searchBarIsEmpty() -> Bool {
// Returns true if the text is empty or nil
return searchController.searchBar.text?.isEmpty ?? true
}
func filterContentForSearchText(_ searchText: String, scope: String = "All") {
filteredBonuses = bonuses.filter({( bonus: JsonFile.JsonBonuses) -> Bool in
return bonus.state.lowercased().contains(searchText.lowercased())
})
tableView.reloadData()
}
func isFiltering() -> Bool {
return searchController.isActive && !searchBarIsEmpty()
}
// MARK: - Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? BonusDetailViewController {
destination.bonus = bonuses[(tableView.indexPathForSelectedRow?.row)!]
}
}
}
extension BonusListViewController: UISearchResultsUpdating {
// MARK: - UISearchResultsUpdating Delegate
func updateSearchResults(for searchController: UISearchController) {
filterContentForSearchText(searchController.searchBar.text!)
}
}
and here is the JsonFile.swift:
import Foundation
struct JsonFile: Codable {
struct Meta: Codable {
let fileName: String
let version: String
}
struct JsonBonuses: Codable {
let bonusCode: String
let category: String
let name: String
let value: Int
let city: String
let state: String
let flavor: String
let imageName: String
}
let meta: Meta
let bonuses: [JsonBonuses]
}
EDIT: The JSON itself can be found at http://www.tourofhonor.com/BonusData.json
Also, on the line that says let bonusSet: JsonFile.JsonBonuses (under the cellForRowAt), I'm getting a warning that says "Immutable value bonusSet was never used; consider removing it" even though I use it in the very next line.
I guess the issue is in your cellForRow method, you are supposed to assignv alues with bonusSet and not bonus. as you are initializing the value from bonus dara structure which should be from bonusSet.
Try changing cellForRow as:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "BonusListViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? BonusListViewCell else {
fatalError("The dequeued cell is not an instance of BonusListViewCell.")
}
let bonus = bonuses[indexPath.row]
let bonusSet: JsonFile.JsonBonuses
if isFiltering() {
bonusSet = filteredBonuses[indexPath.row]
} else {
bonusSet = bonus
}
//CHANGE IS REQUIRED HERE: REPLACE THE bonus WITH bonusSet :
let urlString = "http://tourofhonor.com/appimages/"+(bonusSet.imageName)
let url = URL(string: urlString)
cell.primaryImage.downloadedFrom(url: url!)
cell.nameLabel.text = bonusSet.name.capitalized
cell.bonusCodeLabel.text = bonusSet.bonusCode.localizedUppercase
cell.categoryLabel.text = bonusSet.category
cell.valueLabel.text = "\(bonusSet.value)"
cell.cityLabel.text = "\(bonusSet.city.capitalized),"
cell.stateLabel.text = bonusSet.state.localizedUppercase
return cell
}
The problem is with your cell for row index path
search result you are getting from the filterContentForSearchText you are storing in filteredBonuses but in cellForRowAt you are still setting all your values from
bouns variable
bonus = bonuses[indexPath.row]
if isFiltering() {
bonusSet = filteredBonuses[indexPath.row] //even though you are creating bonusSet you are not using it while setting cell values below so use that bonusSet
} else {
bonusSet = bonus
}
//Like this
let urlString = "http://tourofhonor.com/appimages/"+(bonusSet.imageName)
let url = URL(string: urlString)
cell.primaryImage.downloadedFrom(url: url!)
cell.nameLabel.text = bonus.name.capitalized
cell.bonusCodeLabel.text = bonusSet.bonusCode.localizedUppercase
cell.categoryLabel.text = bonusSet.category
cell.valueLabel.text = "\(bonusSet.value)"
cell.cityLabel.text = "\(bonusSet.city.capitalized),"
cell.stateLabel.text = bonusSet.state.localizedUppercase
This code is useless:
let bonusSet: JsonFile.JsonBonuses
if isFiltering() {
bonusSet = filteredBonuses[indexPath.row]
} else {
bonusSet = bonus
}
You create a local variable bonusSet whose value depends on whether you are filtering; but, as the compiler rightly observes, nothing you do afterwards uses it. Your code thus behaves exactly the same way regardless of whether you are filtering.
import UIKit
import GooglePlaces
import Alamofire
import CoreData
class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return listData?.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CityCollectionViewCell
let city = listData![indexPath.row] as? NSDictionary
let name = city?.object(forKey: "name") as? String
let main = city?.object(forKey: "main") as! NSDictionary
let temp = main.object(forKey: "temp") as? Double
let date1 = city?.object(forKey: "dt")
let date = Date(timeIntervalSince1970: date1 as! TimeInterval)
let dateFormatter = DateFormatter()
dateFormatter.timeZone = TimeZone(abbreviation: "GMT") //Set timezone that you want
dateFormatter.locale = NSLocale.current
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm" //Specify your format that you want
let strDate = dateFormatter.string(from: date)
cell.cityLabel.text = name!
cell.lastUpdatedLabel.text = strDate
cell.tempLabel.text = "\(temp!)"
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
cv.deselectItem(at: indexPath, animated: true)
let row = indexPath.row;
let selectedCity = list![row];
userDefaults?.set(selectedCity, forKey: "citySelection");
self.performSegue(withIdentifier: "selectCity", sender: self);
}
#IBOutlet weak var cv: UICollectionView!
var userDefaults:UserDefaults?;
var list:NSMutableArray?
var listData:NSMutableArray?
let group = DispatchGroup()
override func viewDidLoad() {
super.viewDidLoad()
userDefaults = UserDefaults.standard;
}
#IBAction func addCity(_ sender: Any) {
let autocompleteController = GMSAutocompleteViewController()
autocompleteController.delegate = self
let addressFilter = GMSAutocompleteFilter()
addressFilter.type = .city
autocompleteController.autocompleteFilter = addressFilter
present(autocompleteController, animated: true, completion: nil)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
updateValues()
}
func updateValues() {
let list = getSearchHistory()
print(list)
let count = list.count
if count > 0
{
for item in list {
group.enter()
getData(name: item as! String)
}
group.notify(queue: .main, execute: {
self.cv.reloadData()
})
}
}
func getData(name: String) {
let modified = name.replacingOccurrences(of: " ", with: "+")
let url = "http://api.openweathermap.org/data/2.5/weather?q=\(modified)&APPID=-------"
Alamofire.request(url, method: HTTPMethod.get).responseJSON(completionHandler: {
(response) -> Void in
let city = response.result.value as! NSDictionary;
self.listData?.add(city)
print(self.listData)
self.group.leave()
})
}
func addToSearchHistory(locationName:String) {
let delegate = UIApplication.shared.delegate as! AppDelegate
let managedContext = delegate.persistentContainer.viewContext;
let entity = NSEntityDescription.insertNewObject(forEntityName: "SavedPlaces", into: managedContext)
entity.setValue(locationName, forKey: "name")
do {
try managedContext.save();
}
catch {
print("Core data error");
}
}
func getSearchHistory() -> NSMutableArray {
let returnData = NSMutableArray()
let delegate = UIApplication.shared.delegate as! AppDelegate
let managedContext = delegate.persistentContainer.viewContext;
do {
let req = NSFetchRequest<NSFetchRequestResult>(entityName: "SavedPlaces");
let data = try managedContext.fetch(req) as! [NSManagedObject];
for item in data {
let name = item.value(forKey: "name") as? String;
returnData.add(name!);
}
}
catch {
print("Core data error");
}
return returnData;
}
}
extension ViewController: GMSAutocompleteViewControllerDelegate {
// Handle the user's selection.
func viewController(_ viewController: GMSAutocompleteViewController, didAutocompleteWith place: GMSPlace) {
self.addToSearchHistory(locationName: place.name)
dismiss(animated: true, completion: nil)
}
func viewController(_ viewController: GMSAutocompleteViewController, didFailAutocompleteWithError error: Error) {
// TODO: handle the error.
print("Error: ", error.localizedDescription)
}
// User canceled the operation.
func wasCancelled(_ viewController: GMSAutocompleteViewController) {
dismiss(animated: true, completion: nil)
}
// Turn the network activity indicator on and off again.
func didRequestAutocompletePredictions(_ viewController: GMSAutocompleteViewController) {
UIApplication.shared.isNetworkActivityIndicatorVisible = true
}
func didUpdateAutocompletePredictions(_ viewController: GMSAutocompleteViewController) {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}
}
I am trying to load the values from stored data using core Data, and then looping over strings and calling the api and then adding it to a new array from which I am populating the collection view.
Problem: I am having all the values(Cities names) list populated correctly but after calling the api and call the "UpdateUI" function I am getting only one cell.
gif file
Requests are not yet completed this is an asynchronous task wait i will handle the code for you , this way whenever a new response come that tableView will be reloaded to reflect that
func updateValues() {
let list = getSearchHistory()
if !list.isEmpty
{
for item in list {
getData(name: item as! String)
}
}
}
func getData(name: String) {
let modified = name.replacingOccurrences(of: " ", with: "+")
let rr = NSMutableArray()
let url = "http://api.openweathermap.org/data/2.5/weather?q=\(modified)&APPID=------------------"
Alamofire.request(url, method: HTTPMethod.get).responseJSON(completionHandler: {
(response) -> Void in
let city = response.result.value as! NSDictionary;
rr.add(city)
self.listData.append(rr)
DispatchQueue.main.async
{
self.cv.reloadData()
}
})
}
Also note a very important step in response you overwrite current array not append to it
self.listData = rr
it should be
self.listData.append(rr)
and that causes the permanent display of one item whether a load occurs or not
Also don't forget to initalize listData in viewDidLoad
listData = NSMutableArray()
Try to parse the api like this
-(void)getDataforCity:(NSString*)cityName
{
NSURL*url = [NSURL URLWithString:[NSString stringWithFormat:#"%#?APPID=%#&q=%#",openWeatherMapBaseURL,openWeatherMapAPIKey,cityName]];
[NSURLSession.sharedSession dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if(error == nil)
{
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSDictionary*main = json[#"main"];
NSString*humidity = main[#"humidity"];
NSString*pressure = main[#"pressure"];
NSString*temp = main[#"temp"];
NSString*tempMax = main[#"temp_max"];
NSString*tempMin = main[#"temp_min"];
NSArray*weatherArr = json[#"weather"];
NSDictionary*weather = weatherArr[0];
NSString*description = weather[#"description"];
NSDictionary*wind = json[#"wind"];
NSString*deg = wind[#"deg"];
NSString*speed = wind[#"speed"];
NSLog(#"humidity %# : ",humidity);
NSLog(#"pressure %# : ",pressure);
NSLog(#"temp %# : ",temp);
NSLog(#"tempMax %# : ",tempMax);
NSLog(#"tempMin %# : ",tempMin);
NSLog(#"description %# : ",description);
NSLog(#"deg %# : ",deg);
NSLog(#"speed %# : ",speed);
NSLog(#"dasdasddasdataioioio : %#",json);
}
else
{
NSLog(#"dasdasddasdata : %#",error);
}
}].resume;
}
You can reload the collectionView after each request completes by calling reloadData inside your networking callback, though that's a bit inefficient unless you really need to reload after each item. If you want to wait until all data is loaded before reloading your collectionView you may consider using a DispatchGroup. That could work like this:
let dispatchGroup = DispatchGroup()
func updateValues() {
let list = getSearchHistory()
let count = list.count
if count > 0 {
for item in list {
dispatchGroup.enter() //Indicate that a new process is beginning in the dispatch group
getData(name: item as! String)
}
group.notify(queue: .main) { //When all processes in the group finish this code will execute
self.cv.reloadData()
}
}
}
func getData(name: String) {
let modified = name.replacingOccurrences(of: " ", with: "+")
let url = "http://api.openweathermap.org/data/2.5/weather?q=\(modified)&APPID=------------------"
Alamofire.request(url, method: HTTPMethod.get).responseJSON(completionHandler: { (response) -> Void in
let city = response.result.value as! NSDictionary
self.listData.append(rr)
dispatchGroup.leave() //Indicate that a process is ending in the dispatch group
})
}
I have a segmented control with four different segments and would like to fetch multiple record types at once. Have only managed to fetch one at a time. Would like to fetch all the 4 record types, use segmented control to display them in their corresponding tableviews. Is there anyway way. I'm fairly new to Swift and iOS.
#IBOutlet weak var segmentControl: UISegmentedControl!
let recordType = "WebBooks"
var web = [CKRecord]()
var mobile = [CKRecord]()
var windows = [CKRecord]()
var databases = [CKRecord]()
func fetchBooksFromCloud() {
let cloudContainer = CKContainer.default()
let publicDatabase = cloudContainer.publicCloudDatabase
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: recordType, predicate: predicate)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var returnValue = 0
switch segmentControl.selectedSegmentIndex {
case 0:
returnValue = web.count
default:
break
}
return returnValue
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier:
"CategoriesCell", for: indexPath) as! HomeTableViewCell
switch segmentControl.selectedSegmentIndex {
case 0:
let webBooks = web[indexPath.row]
cell.bookName.text = web.object(forKey: "name") as? String
cell.authorName.text = web.object(forKey: "author") as? String
if let image = web.object(forKey: "image") {
let imageAsset = image as! CKAsset
if let imageData = try? Data.init(contentsOf: imageAsset.fileURL) {
cell.bookImageName.image = UIImage(data: imageData)
}
}
default:
break
}
return cell
}
for more reference : iCloud in Swift
func getResults(){
let container = CKContainer.default()
let privateDatabase = container.privateCloudDatabase
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "UserDetails", predicate: predicate)
privateDatabase.perform(query, inZoneWith: nil) { (results, error) -> Void in
if error != nil {
print(error?.localizedDescription)
MBProgressHUD.hide(for: self.view, animated: true)
}
else {
print(results)
for result in results! {
self.arrayDetails.append(result)
}
OperationQueue.main.addOperation({ () -> Void in
self.tableView.reloadData()
self.tableView.isHidden = false
MBProgressHUD.hide(for: self.view, animated: true)
})
}
}
}
i am having in table view which will display the data from one url.But when i open first time.I am seeing two times same data or some time no data are showing in my table view.
import UIKit
import CoreLocation
class ContainerViewController: UIViewController, CLLocationManagerDelegate, UITableViewDelegate, UITableViewDataSource {
// check the current view controller
weak var currentViewController: UIViewController?
// show the location
#IBOutlet weak var LocationLabel: UILabel!
var locationManager: CLLocationManager = CLLocationManager()
#IBOutlet var TypeLabel: UILabel!
#IBOutlet var TableViewList: UITableView!
var startLocation: CLLocation!
var NewCurrentLatitude: Double!
var NewCurrentLongitude: Double!
var BTdata = BTData?()
var BTypeId : String?
// array to store the value from json
var arrDict = [Businessdata]()
var selectedIndex:NSIndexPath?
override func viewDidLoad() {
super.viewDidLoad()
isSearching = false;
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
startLocation = nil
// nib for custom cell (table view)
let nib = UINib(nibName:"customCell", bundle: nil)
TableViewList.registerNib(nib, forCellReuseIdentifier: "cell")
// LoadBusinesses()
}
override func viewDidAppear(animated: Bool)
{
self.TypeLabel.text = BTdata?.BTNames
self.BTypeId = BTdata?.BTIds
locationManager.startUpdatingLocation()
}
// current location
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location : CLLocationCoordinate2D = manager.location!.coordinate;
CLGeocoder().reverseGeocodeLocation(manager.location!, completionHandler: {(placemarks, error)->Void in
if (error != nil)
{
print("Reverse geocoder failed with error" + error!.localizedDescription)
return
}
if placemarks!.count > 0
{
let pm : CLPlacemark = placemarks![0]
//stop updating location to save battery life
let locality = (pm.locality != nil) ? pm.locality : ""
let state = pm.administrativeArea
let countryCode = pm.ISOcountryCode
if(countryCode == "CAN")
{
self.LocationLabel.text = "in "+locality!+", "+state!
self.NewCurrentLongitude = location.longitude;
self.NewCurrentLatitude = location.latitude;
}
else
{
self.LocationLabel.text = "in Toronto, ON"
self.NewCurrentLatitude = 43.761539;
self.NewCurrentLongitude = -79.411079;
print("Manual location Label.")
}
self.locationManager.stopUpdatingLocation()
}
else
{
print("Problem with the data received from geocoder")
}
self.LoadBusinesses()
NSUserDefaults.standardUserDefaults().setDouble(self.NewCurrentLongitude, forKey: "UserLongitude")
NSUserDefaults.standardUserDefaults().setDouble(self.NewCurrentLatitude, forKey: "UserLatitude")
NSUserDefaults.standardUserDefaults().synchronize()
})
}
// location load failure
func locationManager(manager: CLLocationManager, didFailWithError error: NSError)
{
print("Error while updating location " + error.localizedDescription)
}
// web services method
func LoadBusinesses()
{
print("Inside Load Business")
let token = NSUserDefaults.standardUserDefaults().valueForKey("access_token") as! String
let headers = ["x-access-token": token]
var StringUrl:String = "http:some url"
StringUrl += "?lat=\(self.NewCurrentLatitude)"
StringUrl += "&long=\(self.NewCurrentLongitude)"
print(StringUrl)
let request = NSMutableURLRequest(URL: NSURL(string: StringUrl)!,
cachePolicy: .UseProtocolCachePolicy,
timeoutInterval: 10.0)
request.HTTPMethod = "GET"
request.allHTTPHeaderFields = headers
let session = NSURLSession.sharedSession()
let dataTask = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in
if (error != nil)
{
print(error)
}
else
{
if let json = (try? NSJSONSerialization.JSONObjectWithData(data!, options: [])) as? NSDictionary
{
let success = json["success"] as? Int
if (success == 1)
{
if let reposArray = json["data"] as? [NSDictionary]
{
self.arrDict.removeAll()
for item in reposArray
{
let itemObj = item as? Dictionary<String,AnyObject>
let b_type = itemObj!["business_type"]
// taxis type
if (b_type as? String == self.BTypeId)
{
self.arrDict.append(Businessdata(json:item))
print("load data")
}
}
dispatch_async(dispatch_get_main_queue(),{
self.TableViewList.reloadData()
print("load data 1")
})
}
}
else
{
let message = json["message"] as? String
print(message)
}
}
}
})
dataTask.resume()
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int
{
print("load data 2")
return self.arrDict.count
}
// number of rows
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return 1
}
// calling each cell based on tap and users ( premium / non premium )
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
print("load data 3")
let cell:customCell = self.TableViewList.dequeueReusableCellWithIdentifier("cell") as! customCell
cell.vendorName.text = arrDict[indexPath.row].BusinessName
cell.vendorAddress.text = arrDict[indexPath.row].Address
cell.VendorRating.rating = arrDict[indexPath.row].Rating!
return cell
}
// height of cell based on tap
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
{
if(isTapped == true && selectedIndex == indexPath)
{
return 125.0;
}
return 80.0;
}
// did select row of table view
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
selectedIndex = indexPath;
isTapped = true;
print("load data 4")
// TableViewList.reloadData();
}
}
My full code .In this same problem also i am facing.Showing same data 3 time .In my consloe, the print function is also printing two time.I measn that function executing two times:
load data 2
load data 2
load data 2
Manual location Label.
Inside Load Business
http://some url?lat=43.761539&long=-79.411079
load data
load data 2
load data 1
load data 3
0
1
2
3
4
Your code should be something like this. You should reload your tableView after you have parsed the data.
if let reposArray = json["data"] as? [NSDictionary] {
self.arrDict.removeAll()
for item in reposArray {
let itemObj = item as? Dictionary<String,AnyObject>
let b_type = itemObj!["business_type"]
// taxis type
if (b_type as? String == self.BTypeId) {
self.arrDict.append(Businessdata(json:item))
}
}
dispatch_async(dispatch_get_main_queue(),{
self.TableViewList.reloadData()
})
}