Hello i have two viewControllers, the first with a stepper
var steppers : UIStepper?
#IBOutlet weak var hourLabel: UILabel!
#IBAction func stepper(_ sender: UIStepper) {
hourLabel.text = String(sender.value)
}
and the second with a tableView
import UIKit
import AVFoundation
class SelectClass: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var list : [QCategoryy] = [QCategoryy]()
var audioPlayer : AVAudioPlayer!
var limit = 3
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
tableView.allowsMultipleSelection = true
self.title = "Categories"
list = NearbyPlaces.getCategories()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
list.sort() { $0.views > $1.views}
tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func backTapp(_ sender: Any) {
let audioUrl = NSURL.fileURL(withPath: Bundle.main.path(forResource: "pop_drip", ofType: "m4a")!)
do{
try AVAudioSession.sharedInstance().setActive(true)
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try audioPlayer = AVAudioPlayer(contentsOf: audioUrl)
audioPlayer.prepareToPlay()
audioPlayer.play()
}
catch _ as NSError
{
}
dismiss(animated: true, completion: nil)
}
#IBAction func doneTapp(_ sender: Any) {
let audioUrl = NSURL.fileURL(withPath: Bundle.main.path(forResource: "pop_drip", ofType: "m4a")!)
do{
try AVAudioSession.sharedInstance().setActive(true)
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try audioPlayer = AVAudioPlayer(contentsOf: audioUrl)
audioPlayer.prepareToPlay()
audioPlayer.play()
}
catch _ as NSError
{
}
self.performSegue(withIdentifier: nearbySearchSegueIdentifier, sender: nil)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return list.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let identifier = "CATEGORY_CELL"
let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath)
let selectedIndexPaths = tableView.indexPathsForSelectedRows
let rowIsSelected = selectedIndexPaths != nil && selectedIndexPaths!.contains(indexPath)
/* cell.accessoryType = rowIsSelected ? .checkmark : .none */
cell.accessoryType = list[indexPath.row].isSelected ? .checkmark : .none
cell.textLabel?.text = list[indexPath.row].name
return cell
}
let nearbySearchSegueIdentifier = "goToMcourse"
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)!
cell.accessoryType = .checkmark
/* self.performSegue(withIdentifier: nearbySearchSegueIdentifier, sender: list[indexPath.row]) */
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)!
cell.accessoryType = .none
}
func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
if let sr = tableView.indexPathsForSelectedRows {
if sr.count == limit {
let alertController = UIAlertController(title: "Oops", message:
"You are limited to \(limit) selections", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: {action in
}))
self.present(alertController, animated: true, completion: nil)
return nil
}
}
return indexPath
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == nearbySearchSegueIdentifier {
guard let category = sender as? QCategoryy else {
return
}
if let selectedRows = tableView.indexPathsForSelectedRows {
if let vc = segue.destination as? CourseClass2 {
vc.category = category
}
}
}
}
}
extension QCategoryy {
private static let ketPrefix = "category-"
var views:Int {
get {
return UserDefaults.standard.integer(forKey: QCategoryy.ketPrefix + name)
}
}
func markView() {
UserDefaults.standard.set(views + 1, forKey: QCategoryy.ketPrefix + name)
}
}
in this tableView i can select multiple cells and add a limit to the selection with the var
var limit = 3
and the func
func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
if let sr = tableView.indexPathsForSelectedRows {
if sr.count == limit {
let alertController = UIAlertController(title: "Oops", message:
"You are limited to \(limit) selections", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: {action in
}))
self.present(alertController, animated: true, completion: nil)
return nil
}
}
return indexPath
}
but what i would like to do is to use the stepper in the first viewController to add the limit at the selection of the tableView (in the second viewController), something like
How can i do it?
In the VC with the stepper, override prepareForSegue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? SelectClass {
vc.limit = Int(stepper.value)
}
}
EDIT: You need to make the stepper an outlet of the VC, change the the declaration to this first:
#IBOutlet var stepper: UIStepper!
In the storyboard, right click on your view controller (the one with the yellow icon):
Then this appears:
See the stepper under "Outlets"? Drag from the circle besides the stepper to the stepper on your storyboard:
Related
I am trying to create a ToDoList in swift UI and my deleteTask function is not working properly. I tried a lot of things and none of them worked.
import UIKit
class ViewController: UIViewController {
#IBOutlet var tableView: UITableView!
var toDoTasks = [String]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.title = "SAVED TASKS"
tableView.delegate = self
tableView.dataSource = self
// setup
if !UserDefaults().bool(forKey: "setup")
{
UserDefaults().set(true, forKey: "setup")
UserDefaults().set(0, forKey: "count")
}
updateTasks()
}
func updateTasks()
{
toDoTasks.removeAll()
guard let count = UserDefaults().value(forKey: "count") as? Int else
{
return
}
for x in 0..<count
{
if let task = UserDefaults().value(forKey: "task_\(x+1)") as? String
{
toDoTasks.append(task)
}
}
tableView.reloadData()
}
#IBAction func didTapAdd()
{
let viewController = storyboard?.instantiateViewController(withIdentifier: "entry") as! EntryViewController
viewController.title = "NEW TASK"
viewController.updated =
{
DispatchQueue.main.async {
self.updateTasks()
}
}
navigationController?.pushViewController(viewController, animated: true)
}
}
extension ViewController: UITableViewDelegate
{
// function to select the rows
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let viewController = storyboard?.instantiateViewController(withIdentifier: "task") as! TasksViewController
viewController.title = "NEW TASK"
viewController.task = toDoTasks[indexPath.row]
navigationController?.pushViewController(viewController, animated: true)
}
}
extension ViewController: UITableViewDataSource
{
// function which returns number of tasks
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return toDoTasks.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = toDoTasks[indexPath.row]
return cell
}
}
import UIKit
class TasksViewController: UIViewController {
#IBOutlet var label : UILabel!
var task : String?
var currentPosition: Int?
override func viewDidLoad() {
super.viewDidLoad()
label.text = task
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Delete", style: .done, target: self, action: #selector(deleteTask))
}
#objc func deleteTask(_ sender: UIBarButtonItem) {
guard let currentPosition = self.currentPosition else {
return
}
let count = UserDefaults.standard.integer(forKey: "count")
UserDefaults.standard.removeObject(forKey: "task_\(currentPosition+1)")
for i in currentPosition+1..<count {
let task = UserDefaults.standard.string(forKey: "task_\(i+1)")
UserDefaults.standard.setValue(task, forKey: "task_\(i)")
}
UserDefaults.standard.setValue(count-1, forKey: "count")
navigationController?.popViewController(animated: true)
}
}
When I press the Delete button, nothing happens. Could you please help me??
So I have a custom SwipeCellTableView class that I inherited from when using UITableViewControllers. Now I want to just use that class for an ib outlet table view controller in a regular View Controller. It is proving to be very difficult and seemingly not worth it anymore. Can this be done?
Here is the superclass which inherits from a TableViewController, I have tried to change it to inherit from a view controller but it just doesn't work out
class SwipeTableViewController: UITableViewController, SwipeTableViewCellDelegate {
var cell: UITableViewCell?
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = 80.0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! SwipeTableViewCell
cell.delegate = self
return cell
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]? {
guard orientation == .right else { return nil }
let deleteAction = SwipeAction(style: .destructive, title: "Delete") { action, indexPath in
// handle action by updating model with deletion
self.updateModel(at: indexPath)
}
deleteAction.image = UIImage(named: "delete-icon")
return [deleteAction]
}
func tableView(_ tableView: UITableView, editActionsOptionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> SwipeOptions {
var options = SwipeTableOptions()
options.expansionStyle = .destructive
//options.transitionStyle = .reveal
return options
}
func updateModel(at indexPath: IndexPath){
//update data model
print("Item deleted from super class")
}
Here is the View Controller I'm trying to access it from:
class GoalsViewController: UIViewController, SwipeTableViewController {
#IBOutlet weak var categoryTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func addCategoryPressed(_ sender: UIButton) {
performSegue(withIdentifier: "showgoalsSeg", sender: self)
}
For reference on how I was using it before when using an actual TableViewController:
class CategoryViewController: SwipeTableViewController {
var categories: Results<Category>? //optional so we can be safe
override func viewDidLoad() {
super.viewDidLoad()
loadCategory()
tableView.rowHeight = 80.0
tableView.separatorStyle = .none
}
//MARK: - Tableview Datasource Methods
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//Only get the count of categories if it's nil, else 1
return categories?.count ?? 1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//fetching cell from super view
let cell = super.tableView(tableView, cellForRowAt: indexPath)
cell.textLabel?.text = categories?[indexPath.row].name ?? "No Categories Added Yet"
cell.backgroundColor = UIColor(hexString: categories?[indexPath.row].color ?? "000000")
return cell
}
//MARK: - Tableview Delegate Methods
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "goToItems", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destinationVC = segue.destination as! ToDoListViewController
if let indexPath = tableView.indexPathForSelectedRow {
destinationVC.selectedCategory = categories?[indexPath.row]
}
}
//MARK: - Add New Categories
#IBAction func addButtonPressed(_ sender: Any) {
var textField = UITextField()
let alert = UIAlertController(title: "Add New Category", message: "", preferredStyle: .alert)
let action = UIAlertAction(title: "Add Category", style: .default) { (action) in
let newCategory = Category()
newCategory.name = textField.text!
newCategory.color = UIColor.randomFlat.hexValue()
self.save(category: newCategory)
}
alert.addAction(action)
alert.addTextField { (field) in
textField = field
textField.placeholder = "Add a new category"
}
present(alert, animated: true, completion: nil)
}
func save(category: Category){
let realm = try! Realm()
do {
try realm.write{
realm.add(category)
}
} catch {
print("error saving context")
}
tableView.reloadData()
}
override func updateModel(at indexPath: IndexPath) {
super.updateModel(at: indexPath)
let realm = try! Realm()
if let categoryForDeletion = self.categories?[indexPath.row]{
do{
try realm.write{
realm.delete(categoryForDeletion)
}
} catch {
print("error deleting cell")
}
//tableView.reloadData()
}
}
func loadCategory(){
let realm = try! Realm()
categories = realm.objects(Category.self)
tableView.reloadData()
}
Is this even worth persuing? Or doable?
EDIT: Just changing the code to show my attempt at the Singleton suggestion.
So I am attempting to create an application which would take the selection of a user from a UITableView and pass that to another UITableView. Then, it would be appended to the array in that view and presented as the new table. The idea being that users can select from multiple lists and create one list made of all their selections.
However, I am extremely new to iOS development and while I can get it to let me take the selected value and show it in the new UITableView, it only sends the one value and does not keep or append it. Meaning I can never show multiple additions to the list.
So, what I'm getting atm is the ability to select a cell, let's say "Kevin Smith", and that value gets sent to the new UITableView and shown to the user. But if I go and select another value, "John Smith", then only John shows up and Kevin is gone.
Here is my three controllers involved:
The first UITAbleView Controller
class PlayerViewController: UITableViewController {
var resultsController: NSFetchedResultsController<Player>!
let CDSPlayer = coreDataStackPlayer()
override func viewDidLoad() {
super.viewDidLoad()
tableView.allowsMultipleSelection = true
let request: NSFetchRequest<Player> = Player.fetchRequest()
let sortDescriptor = NSSortDescriptor(key: "level", ascending: true)
request.sortDescriptors = [sortDescriptor]
resultsController = NSFetchedResultsController(
fetchRequest: request,
managedObjectContext: CDSPlayer.managedContext,
sectionNameKeyPath: nil,
cacheName: nil
)
resultsController.delegate = self
do{
try resultsController.performFetch()
} catch {
print("Perform Fetch Error: \(error)")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return resultsController.sections?[section].numberOfObjects ?? 0
}
override func tableView(_ _tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCell(withIdentifier: "PlayerCell", for: indexPath)
let selectedIndexPaths = tableView.indexPathsForSelectedRows
let rowIsSelected = selectedIndexPaths != nil && selectedIndexPaths!.contains(indexPath)
cell.accessoryType = rowIsSelected ? .checkmark : .none
let player = resultsController.object(at: indexPath)
cell.textLabel?.text = player.name
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "player2form", sender: tableView.cellForRow(at: indexPath))
}
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let action = UIContextualAction(style:.destructive, title: "Delete"){(action, view, completion) in
let player = self.resultsController.object(at: indexPath)
self.resultsController.managedObjectContext.delete(player)
do {
try self.resultsController.managedObjectContext.save()
self.errorMessage(message: "Player Character Deleted.");
completion(true)
} catch {
print("Delete Failed: \(error)")
self.errorMessage(message: "Failed to Delete Player Character.");
completion(false)
}
}
action.image = UIImage(named: "trash")
action.backgroundColor = .red
return UISwipeActionsConfiguration(actions: [action])
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?){
if let _ = sender as? UIBarButtonItem, let vc = segue.destination as? AddPlayerViewController{
vc.managedContext = resultsController.managedObjectContext
}
if let cell = sender as? UITableViewCell, let vc = segue.destination as? AddPlayerViewController{
vc.managedContext = resultsController.managedObjectContext
if let indexPath = tableView.indexPath(for: cell){
let player = resultsController.object(at: indexPath)
Service.shared.allPlayers.append(player)
vc.player = player
}
}
}
//Error Function
func errorMessage(message:String){
let alert = UIAlertController(title: "Alert", message: message, preferredStyle:UIAlertControllerStyle.alert);
let okAction = UIAlertAction(title:"Ok", style:UIAlertActionStyle.default, handler:nil);
alert.addAction(okAction);
self.present(alert, animated: true, completion:nil);
}
}
extension PlayerViewController: NSFetchedResultsControllerDelegate{
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type{
case .insert:
if let indexPath = newIndexPath{
tableView.insertRows(at: [indexPath], with: .automatic)
}
case .delete:
if let indexPath = indexPath{
tableView.deleteRows(at: [indexPath], with: .automatic)
}
case .update:
if let indexPath = indexPath, let cell = tableView.cellForRow(at: indexPath){
let player = resultsController.object(at: indexPath)
cell.textLabel?.text = player.name
}
default:
break
}
}
The Selection view that passes the data:
class AddPlayerViewController: UIViewController {
var managedContext: NSManagedObjectContext!
var player: Player?
var selectedItems = [String]()
#IBOutlet weak var done_btn: UIButton!
#IBOutlet weak var playerInput: UITextField!
#IBOutlet weak var cancel_btn: UIButton!
#IBAction func add2combat(_ sender: UIButton) {
self.performSegue(withIdentifier: "player2combat", sender: self)
}
#IBAction func done(_ sender: UIButton) {
guard let name = playerInput.text, !name.isEmpty else {
return //Add notice user cannot save empty items
}
if let player = self.player {
player.name = name
} else {
//Set values from input to the cell
let player = Player(context: managedContext)
player.name = name
}
do {
try managedContext.save()
playerInput.resignFirstResponder()
dismiss(animated: true)
} catch {
print("Error Saving Player Character: \(error)")
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?){
if segue.identifier == "player2combat" {
let selectedItems : [String] = [playerInput.text!]
let otherVc = segue.destination as! CombatSceneViewController
otherVc.selectedItems = selectedItems
print(selectedItems)
}
}
#IBAction func cancel(_ sender: UIButton) {
playerInput.resignFirstResponder()
dismiss(animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
playerInput.becomeFirstResponder()
if let player = player {
playerInput.text = player.name
playerInput.text = player.name
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
And the UITableView that gets the data and tries to append it to the existing Array.
class CombatSceneViewController: UITableViewController{
var selectedItems = Service.shared.allSelectedItems
override func viewDidLoad() {
super.viewDidLoad()
let otherVC = AddPlayerViewController()
selectedItems.append(contentsOf: otherVC.selectedItems)
saveData()
loadData()
print(selectedItems)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return selectedItems.count
}
override func tableView(_ _tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCell(withIdentifier: "CombatCell", for: indexPath as IndexPath)
cell.textLabel?.text = selectedItems[indexPath.item]
return cell
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
tableView.reloadData()
}
func saveData() {
let data = NSMutableData()
// 1
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let path = paths[0]
let file = (path as NSString).appendingPathComponent("Persistent.plist")
//2
let archiver = NSKeyedArchiver(forWritingWith: data)
archiver.encode(selectedItems, forKey: "Agents")
archiver.finishEncoding()
data.write(toFile: file, atomically: true)
}
func loadData() {
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let path = paths[0]
let file = (path as NSString).appendingPathComponent("Persistent.plist")
// 1
if FileManager.default.fileExists(atPath: file) {
if let data = NSData(contentsOfFile: file) {
let unarchiver = NSKeyedUnarchiver(forReadingWith: data as Data)
selectedItems = unarchiver.decodeObject(forKey: "Agents") as! [String]
unarchiver.finishDecoding()
}
}
}
Singleton Class:
class Service {
static let shared = Service()
var allPlayers = [Player]()
var allSelectedItems = [String]()
}
I hope I did the formatting right... this is my first post on here, be gentle^^;.
The problem is that when you go back the old player is gone because you don't keep it , you can try to create a singleton for that
class Service {
static let shared = Service()
var allPlayers = [Player]()
var allSelectedItems = [String]()
}
// keep it here
if let cell = sender as? UITableViewCell, let vc = segue.destination as? AddPlayerViewController{
vc.managedContext = resultsController.managedObjectContext
if let indexPath = tableView.indexPath(for: cell){
let player = resultsController.object(at: indexPath)
Service.shared.allPlayers.append(player) // add this line
vc.player = player
}
}
Do same logic to append selected items and when you reach final tableView access them with
Service.shared.allPlayers
Or
Service.shared.selectedItems
That way you have a persisted container for all the selected players & items accessible anyWhere inside the app
I have a UiViewController with a tableView, this tableView has a list of places (googlePlaces) that I can select (such as restaurants, cinemas, bar) and then tap a button to go on in the next controller where I expect to see a list of places of the type I have chosen; the problem is that it does not leave places for all the selected categories, for example if I had select cinema, bar and restaurant, one time it shows me only restaurants, the other only the cinemas, in a completely casual manner. Here is my prepare
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == nearbySearchSegueIdentifier {
let selectedCategories: [QCategoryy] = tableView.indexPathsForSelectedRows?.map({ (indexPath) -> QCategoryy in
return list[indexPath.row] }) ?? []
if let selectedRows = tableView.indexPathsForSelectedRows {
if let vc : CourseClass2 = segue.destination as? CourseClass2 {
vc.categories = selectedCategories
}
}
}
}
and this is the next viewController
import UIKit
import CoreLocation
import Social
import AVFoundation
private let resueIdentifier = "MyTableViewCell"
extension UIViewController {
func present(viewController : UIViewController, completion : (() -> ())? = nil ){
if let presented = self.presentedViewController {
presented.dismiss(animated: true, completion: {
self.present(viewController, animated: true, completion: completion)
})
} else {
self.present(viewController, animated: true, completion: completion)
}
}
}
class CourseClass2: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var locationManager:CLLocationManager?
let minimumSpacing : CGFloat = 15 //CGFloat(MAXFLOAT)
let cellWidth: CGFloat = 250
let radius = 5000 // 5km
var categories: [QCategoryy?]? = []
var currentLocation : CLLocationCoordinate2D?
var places: [QPlace] = []
var isLoading = false
var response : QNearbyPlacesResponse?
var rows = 0
var numberPlaces = 0
override func viewDidLoad() {
super.viewDidLoad()
for category in categories! {
title = category?.name
}
tableView.dataSource = self
tableView.delegate = self
numberPlaces = HomeClass.globalLimit
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
determineMyCurrentLocation()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
rows = 0
tableView.reloadData()
for category in categories! {
category?.markView()
}
}
#IBAction func refreshTapped(_ sender: Any) {
rows = 0
print("numberOfRows Call", self.numberPlaces)
tableView.reloadData()
}
func canLoadMore() -> Bool {
if isLoading {
return false
}
if let response = self.response {
if (!response.canLoadMore()) {
return false
}
}
return true
}
func loadPlaces(_ force:Bool) {
if !force {
if !canLoadMore() {
return
}
}
print("load more")
isLoading = true
for category in categories! {
NearbyPlaces.getNearbyPlaces(by: category?.name ?? "food", coordinates: currentLocation!, radius: radius, token: self.response?.nextPageToken, completion: didReceiveResponse)
}
}
func didReceiveResponse(response:QNearbyPlacesResponse?, error : Error?) -> Void {
if let error = error {
let alertController = UIAlertController(title: "Error", message: error.localizedDescription, preferredStyle: .alert)
let actionDismiss = UIAlertAction(title: "Dismiss", style: .cancel, handler: nil)
let actionRetry = UIAlertAction(title: "Retry", style: .default, handler: { (action) in
DispatchQueue.main.async {
self.loadPlaces(true)
}
})
alertController.addAction(actionRetry)
alertController.addAction(actionDismiss)
DispatchQueue.main.async {
self.present(viewController: alertController)
}
}
if let response = response {
self.response = response
if response.status == "OK" {
if let placesDownloaded = response.places {
places.append(contentsOf: placesDownloaded)
}
self.tableView?.reloadData()
} else {
let alert = UIAlertController.init(title: "Error", message: response.status, preferredStyle: .alert)
alert.addAction(UIAlertAction.init(title: "Cancel", style: .cancel, handler: nil))
alert.addAction(UIAlertAction.init(title: "Retry", style: .default, handler: { (action) in
DispatchQueue.main.async {
self.loadPlaces(true)
}
}))
self.present(viewController: alert)
}
isLoading = false
}
else {
print("response is nil")
}
}
func numberOfSections(in tableView: UITableView) -> Int {
print("numberOfsection Call")
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("numberOfRows Call")
if places.count < self.numberPlaces {
return places.count /* rows */
}
return self.numberPlaces
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: resueIdentifier, for: indexPath) as! MyTableViewCell
let place = places[indexPath.row]
cell.update(place: place)
if indexPath.row == places.count - 1 {
loadPlaces(false)
}
print("CellForRow Call")
return (cell)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
UIView.animate(withDuration: 0.2, animations: {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyTableViewCell", for: indexPath) as! MyTableViewCell
})
performSegue(withIdentifier: "goToLast" , sender: indexPath)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == UITableViewCellEditingStyle.delete {
places.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
What I have to do to make that if had selected more than one category of places, in the tableView of the next viewController shows places for each selected category? (since there is a limit of places that can be shown represented by numberPlaces = HomeClass.globalLimit the best solution it would be to have at least one place for each selected category and others added randomly)
EDIT
here where is the indexPathsForSelectedRows
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let identifier = "CATEGORY_CELL"
let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath)
let selectedIndexPaths = tableView.indexPathsForSelectedRows
let rowIsSelected = selectedIndexPaths != nil && selectedIndexPaths!.contains(indexPath)
/* cell.accessoryType = rowIsSelected ? .checkmark : .none */
cell.accessoryType = list[indexPath.row].isSelected ? .checkmark : .none
cell.textLabel?.text = list[indexPath.row].name
return cell
}
Apparently your problem is the architecture of your code. On loadPlaces you are iterating through your categories and doing several network calls. Then you append those results to places and use reloadData to reload the table, but on cellForRowAt you call loadPlaces again.
Even that you set isLoading = true inside loadPlaces you have multiple requests going on and all of them set isLoading = false at the end. So at some point you will have some unexpected result. You also have some force load cases that add up to all that.
Last but not least, since you are calling self.tableView?.reloadData() inside a closure, it its possible that its not updating correctly.
TL;DR
Wrap your reloadData around a DispatchQueue.main.async block.
Implement a queue that serialises your network requests to put some order around your calls. You can use a library like this for example.
let queue = TaskQueue()
for category in categories {
queue.tasks +=~ { result, next in
// Add your places request here
}
queue.tasks +=! {
// Reload your table here
}
queue.run {
// check your places array is correct
}
}
Other observations:
Your title is going to be always the last category on categories, since you are not using all the array on title = category?.name.
To better understand whats going on, try to select only 2 categories and to see if there is a patter on which one is loaded (always the first, or always the second). If there is no pattern at all its because the problem is for sure networking.
I have two viewControllers. The first with a stepper:
var steppers : UIStepper?
#IBOutlet weak var hourLabel: UILabel!
#IBOutlet weak var stepper: UIStepper!
#IBAction func stepper(_ sender: UIStepper) {
hourLabel.text = String(sender.value)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let np = segue.destination as? CourseClass2 {
np.numberPlaces = Int(stepper.value)
}
}
and the second with a tableView:
private let resueIdentifier = "MyTableViewCell"
extension UIViewController {
func present(viewController : UIViewController, completion : (() -> ())? = nil ){
if let presented = self.presentedViewController {
presented.dismiss(animated: true, completion: {
self.present(viewController, animated: true, completion: completion)
})
} else {
self.present(viewController, animated: true, completion: completion)
}
}
}
class CourseClass2: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var locationManager:CLLocationManager?
let minimumSpacing : CGFloat = 15 //CGFloat(MAXFLOAT)
let cellWidth: CGFloat = 250
let radius = 5000 // 5km
var category : QCategoryy?
var currentLocation : CLLocationCoordinate2D?
var places: [QPlace] = []
var isLoading = false
var response : QNearbyPlacesResponse?
var rows = 0
var numberPlaces = 0
override func viewDidLoad() {
super.viewDidLoad()
self.title = category?.name
tableView.dataSource = self
tableView.delegate = self
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
determineMyCurrentLocation()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
rows = 0
/* insertRowsMode3()
tableView.reloadData() */
category?.markView()
}
#IBAction func refreshTapped(_ sender: Any) {
rows = 0
/* insertRowsMode3() */
tableView.reloadData()
}
func canLoadMore() -> Bool {
if isLoading {
return false
}
if let response = self.response {
if (!response.canLoadMore()) {
return false
}
}
return true
}
func loadPlaces(_ force:Bool) {
if !force {
if !canLoadMore() {
return
}
}
print("load more")
isLoading = true
NearbyPlaces.getNearbyPlaces(by: category?.name ?? "food", coordinates: currentLocation!, radius: radius, token: self.response?.nextPageToken, completion: didReceiveResponse)
}
func didReceiveResponse(response:QNearbyPlacesResponse?, error : Error?) -> Void {
if let error = error {
let alertController = UIAlertController(title: "Error", message: error.localizedDescription, preferredStyle: .alert)
let actionDismiss = UIAlertAction(title: "Dismiss", style: .cancel, handler: nil)
let actionRetry = UIAlertAction(title: "Retry", style: .default, handler: { (action) in
DispatchQueue.main.async {
self.loadPlaces(true)
}
})
alertController.addAction(actionRetry)
alertController.addAction(actionDismiss)
DispatchQueue.main.async {
self.present(viewController: alertController)
}
}
if let response = response {
self.response = response
if response.status == "OK" {
if let placesDownloaded = response.places {
places.append(contentsOf: placesDownloaded)
}
self.tableView?.reloadData()
} else {
let alert = UIAlertController.init(title: "Error", message: response.status, preferredStyle: .alert)
alert.addAction(UIAlertAction.init(title: "Cancel", style: .cancel, handler: nil))
alert.addAction(UIAlertAction.init(title: "Retry", style: .default, handler: { (action) in
DispatchQueue.main.async {
self.loadPlaces(true)
}
}))
self.present(viewController: alert)
}
isLoading = false
}
else {
print("response is nil")
}
}
func numberOfSections(in tableView: UITableView) -> Int {
print("numberOfsection Call")
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("numberOfRows Call")
return places.count /* rows */
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: resueIdentifier, for: indexPath) as! MyTableViewCell
let place = places[indexPath.row]
cell.update(place: place)
if indexPath.row == places.count - 1 {
loadPlaces(false)
}
print("CellForRow Call")
return (cell)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
UIView.animate(withDuration: 0.2, animations: {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyTableViewCell", for: indexPath) as! MyTableViewCell
})
performSegue(withIdentifier: "goToLast" , sender: indexPath.row)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == UITableViewCellEditingStyle.delete {
places.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
func didReceiveUserLocation(_ userLocation:CLLocation) {
currentLocation = userLocation.coordinate
loadPlaces(true)
}
I added only the code useful for the question. As you can see I'm trying to pass from the first VC to the second this value:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let np = segue.destination as? CourseClass2 {
np.numberPlaces = Int(stepper.value)
}
}
Because I would like to add a limit to the number of cells that spawn when loading the tableview in the second viewController (and I want this limit to be chosen by the user with the stepper in the previous controller), how do I have to modify the tableView to make sure that the number of cells generated is equal to the value of var numberPlaces?
I would suggest that you limit the number of cells inside UITableView delegate method number of cells
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("numberOfRows Call")
if places.count < self.numberPlaces {
return places.count /* rows */
}
return self.numberPlaces
}
In this way, you will get right number of cells, but if there would be too many places only self.numberPlaces will be displayed.
Maybe, you would need to figure out some sorting, so that correct places would be displayed. (By proximity,...)