UiTableView number of generated cells - ios

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,...)

Related

Swift - can't reload Data from TableView after add button action

I'm new to Swift and need your help.
I created a TableViewController with a custom cell.
Also I created a "add" Button in navigation bar to add a new value to my tableview.
Saving the values in Core Data and fetch them in viewWillAppear.
When pressing the add button a UIAlertController shows up which i had customized like i needed. I added a cancel action and a ok action but when i press the ok button from the alert the new value don't shows up in my tableview. I have to switch to an other viewcontroller that the tableview shows it.
I added groupsTableView.reloadData()on different points in my code but cant get it to work.
Hope someone can help me out!
Code from MasterViewController:
import UIKit
import CoreData
class MasterViewController: UITableViewController {
var groups: [Groups] = []
#IBOutlet weak var groupsTableView: UITableView!
var groupsTextField: UITextField?
override func viewDidLoad() {
super.viewDidLoad()
groupsTableView.delegate = self
groupsTableView.dataSource = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func viewWillAppear(_ animated: Bool) {
// Core date initialization
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest: NSFetchRequest<Groups> = Groups.fetchRequest()
do {
groups = try managedContext.fetch(fetchRequest)
groupsTableView.reloadData()
} catch {
// TODO: error handling
print("Could not fetch groups")
}
navigationItem.leftBarButtonItem = editButtonItem
let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(insertNewObject))
navigationItem.rightBarButtonItem = addButton
}
// MARK: - add new Group
#objc func insertNewObject() {
let addButtonAlert = UIAlertController(title: "Neue Gruppe", message: "Füge eine neue Gruppe deiner Liste hinzu", preferredStyle: .alert)
addButtonAlert.addTextField { (UITextField) in
self.groupsTextField = UITextField
self.groupsTextField?.placeholder = "Name der Gruppe"
self.groupsTextField?.clearButtonMode = .whileEditing
}
let okAction = UIAlertAction(title: "Hinzufügen", style: .default, handler: addNewGroup)
let cancelAction = UIAlertAction(title: "Abbrechen", style: .cancel, handler: nil)
addButtonAlert.addAction(okAction)
addButtonAlert.addAction(cancelAction)
self.present(addButtonAlert, animated: true, completion: nil)
}
func addNewGroup(_:UIAlertAction) -> Void {
let group = Groups(groupId: UUID(), groupTitle: groupsTextField!.text ?? "")
do {
try group?.managedObjectContext?.save()
groupsTableView.reloadData()
} catch {
// TODO: error handling
print("Could not save group")
}
}
// MARK: - Segue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let destination = segue.destination as? DetailViewController,
let selectedRow = self.groupsTableView.indexPathForSelectedRow?.row else {
return
}
destination.group = groups[selectedRow]
destination.title = groups[selectedRow].groupTitle
}
// MARK: - delete Group
func deleteGroup(at indexPath: IndexPath) {
let group = groups[indexPath.row]
guard let managedContext = group.managedObjectContext else {
return
}
managedContext.delete(group)
do {
try managedContext.save()
groups.remove(at: indexPath.row)
groupsTableView.deleteRows(at: [indexPath], with: .automatic)
} catch {
//TODO: error handling
print("Could not delete Group")
groupsTableView.reloadRows(at: [indexPath], with: .automatic)
}
}
// MARK: - Table View
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return groups.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = groupsTableView.dequeueReusableCell(withIdentifier: "GroupsTableViewCell", for: indexPath) as! GroupsTableViewCell
let object = groups[indexPath.row]
cell.groupTitleLabel?.text = object.groupTitle
return cell
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
deleteGroup(at: indexPath)
}
}
}
Add group item to your groups array and after that reload your tableview as shown below:-
func addNewGroup(_:UIAlertAction) -> Void {
let group = Groups(groupId: UUID(), groupTitle: groupsTextField!.text ?? "")
do {
try group?.managedObjectContext?.save()
self.groups.append(group)
groupsTableView.reloadData()
} catch {
// TODO: error handling
print("Could not save group")
}
}

How do You Inherit a Custom TableViewController For An TableView in a ViewController

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?

I select a list of place categories in a tableview but in the segue it only sends one

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.

CollectionView vs TableView (Swift)

I have two classes that do exactly the same thing, load places of a chosen category near the user's location but one uses a collectionView and the other uses a tableView (and a particular animation).
I added only the part of code that serves the question, and although some names are different, all links with the storyboards and other classes are the same as you can see much of the code is very similar between these two classes.
My problem is that the class with collectionView work perfectly, but the class using the tableView does not show the cells of the tableView (as if it did not load it properly) if anyone could see if there is a mistake, I would be grateful.
This is the class using the collectionView :
import UIKit
import CoreLocation
import AVFoundation
private let reuseIdentifier = "PlacesCell"
extension UIViewController {
func present(viewController : UIViewController, completion : (() -> ())? = nil ){
if let presented = self.presentedViewController {
presented.removeFromParentViewController()
}
self.present(viewController, animated: true, completion: completion)
}
}
class NearbyPlacesViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
var locationManager:CLLocationManager?
let minimumSpacing : CGFloat = 15 //CGFloat(MAXFLOAT)
let cellWidth: CGFloat = 250
let radius = 5000 // 5km
var category : QCategory?
var currentLocation : CLLocationCoordinate2D?
var places: [QPlace] = []
var isLoading = false
var response : QNearbyPlacesResponse?
override func viewDidLoad() {
super.viewDidLoad()
//self.collectionView
self.title = category?.name
collectionView?.contentInset = UIEdgeInsets.init(top: 0, left: minimumSpacing, bottom: 0, right: minimumSpacing)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
determineMyCurrentLocation()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
category?.markView()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
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
NearbyPlacesController.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.collectionView?.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")
}
}
// MARK: UICollectionViewDataSource
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return places.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! PlacesCell
let place = places[indexPath.row]
cell.update(place: place)
if indexPath.row == places.count - 1 {
loadPlaces(false)
}
return cell
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
performSegue(withIdentifier: "maps-vc", sender: indexPath)
}
// MARK: UICollectionViewDelegateFlowLayoutDelegate
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return minimumSpacing
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let cellPadding: CGFloat = 20.0
let columnWidth:CGFloat = cellWidth
let imageWidth = columnWidth
let labelWidth = columnWidth - cellPadding * 2
let photoHeight = heightForPhotoAtIndexPath(indexPath: indexPath, withWidth: imageWidth)
let annotationHeight = heightForAnnotationAtIndexPath(indexPath: indexPath, withWidth: labelWidth)
let height = photoHeight + annotationHeight
return CGSize.init(width: columnWidth, height: height)
}
// Calculates the height of photo
func heightForPhotoAtIndexPath(indexPath: IndexPath,
withWidth width: CGFloat) -> CGFloat {
var size = CGSize.init(width: CGFloat(MAXFLOAT), height: 1)
let place = places[indexPath.row]
guard let photo = place.photos?.first, place.photos?.first?.photoRef != nil else {
return 0
}
size = CGSize.init(width: CGFloat(photo.width!), height: CGFloat(photo.height!))
let boundingRect = CGRect(x: 0, y: 0, width: width, height: CGFloat(MAXFLOAT))
let rect = AVMakeRect(aspectRatio: size, insideRect: boundingRect)
return rect.size.height
}
// Calculates the height label
func heightForAnnotationAtIndexPath(indexPath: IndexPath, withWidth width: CGFloat) -> CGFloat {
let place = places[indexPath.row]
let annotationPadding = CGFloat(5)
let font = UIFont.systemFont(ofSize: 15)
let commentHeight = place.heightForComment(font, width: width)
let height = annotationPadding + commentHeight + annotationPadding
return height
}
// did receive location
func didReceiveUserLocation(_ userLocation:CLLocation) {
currentLocation = userLocation.coordinate
loadPlaces(true)
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "maps-vc" && sender is IndexPath {
let dvc = segue.destination as! MapViewController
dvc.index = (sender as! IndexPath).row
dvc.places = places
dvc.userLocation = currentLocation
}
}
}
This is the class using the tableView:
import UIKit
import MapKit
import CoreLocation
import GoogleMaps
import GooglePlaces
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 category : QCategoryy?
var currentLocation : CLLocationCoordinate2D?
var places: [QPlace] = []
var isLoading = false
var response : QNearbyPlacesResponse?
var rows = 0
override func viewDidLoad() {
super.viewDidLoad()
self.title = category?.name
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
determineMyCurrentLocation()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillAppear(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 insertRowsMode2() {
tableView.beginUpdates()
for i in 0..<places.count {
insertRowMode2(ind: i, usr: places[i])
}
tableView.endUpdates()
}
func insertRowMode2(ind:Int,usr:QPlace) {
tableView.beginUpdates()
let indPath = IndexPath(row: ind, section: 0)
rows = ind + 1
tableView.insertRows(at: [indPath], with: .right)
tableView.endUpdates()
}
func insertRowsMode3() {
tableView.beginUpdates()
rows = 0
insertRowMode3(ind: 0)
tableView.endUpdates()
}
func insertRowMode3(ind:Int) {
tableView.beginUpdates()
let indPath = IndexPath(row: ind, section: 0)
rows = ind + 1
tableView.insertRows(at: [indPath], with: .right)
guard ind < places.count-1 else { return }
DispatchQueue.main.asyncAfter(deadline: .now()+0.20) {
self.insertRowMode3(ind: ind+1)
}
tableView.endUpdates()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return places.count /* rows */
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MyTableViewCell
let place = places[indexPath.row]
cell.update(place: place)
if indexPath.row == places.count - 1 {
loadPlaces(false)
}
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: "cell", 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)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToLast" && sender is IndexPath {
let dvc = segue.destination as! FinalClass
dvc.index = (sender as! IndexPath).row
dvc.places = places
dvc.userLocation = currentLocation
}
}
#IBAction func IndTapped(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
Did you set the CourseClass2 class as the delegate/dataSource for your tableView?
Try adding
tableView.dataSource = self
tableView.delegate = self
to your viewDidLoad method.
Almost every time I run into an issue like yours is because I forgot to set the delegate or dataSource.
Edit: I had previously only added the delegate on my answer, thanks Dennis for reminding me of the dataSource.
I saw your are doing an async update, but a synced reloadData...
You will have to do the update inside the asyncAfter, or add a closure to your insertRowMode3 and reloadData in that closure...
Could you try this?
func insertRowMode3(ind:Int) {
DispatchQueue.main.asyncAfter(deadline: .now()+0.20) {
tableView.reloadData()
}
}

TableView limit use (Swift)

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:

Resources