My application is crushing when i come in this controller by the error: Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to insert row 0 into section 0, but there are only 0 rows in section 0 after the update'
so i have a problem with the tableView, the class is this:
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.removeFromParentViewController()
}
self.present(viewController, animated: true, completion: completion)
}
}
class CourseClass2: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
struct User {
var name: String
var images: UIImage
var type: String
}
var previuosViewTappedButtonsArray = [String]()
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 users = [User]()
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
tableView.reloadData()
insertRowsMode3()
category?.markView()
}
#IBAction func refreshTapped(_ sender: Any) {
rows = 0
tableView.reloadData()
insertRowsMode3()
}
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() {
for i in 0..<places.count {
insertRowMode2(ind: i, usr: places[i])
}
}
func insertRowMode2(ind:Int,usr:QPlace) {
let indPath = IndexPath(row: ind, section: 0)
rows = ind + 1
tableView.insertRows(at: [indPath], with: .right)
}
func insertRowsMode3() {
rows = 0
insertRowMode3(ind: 0)
}
func insertRowMode3(ind:Int) {
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)
}
}
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)
}
/* let user = users[indexPath.row]
cell.selectionStyle = .none
cell.myImage.image = user.images
cell.myLabel.text = user.name
cell.myTypeLabel.text = user.type */
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: users[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
/* guard let vc = segue.destination as? FinalClass else { return }
let guest = segue.destination as! FinalClass
if let user = sender as? User {
*/
}
}
#IBAction func IndTapped(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func socialShare(_ sender: Any) {
//Alert
let alert = UIAlertController(title: "Share", message: "First share!", preferredStyle: .actionSheet)
//First action
let actionOne = UIAlertAction(title: "Share on Facebook", style: .default) { (action) in
//Checking if user is connected to Facebook
if SLComposeViewController.isAvailable(forServiceType: SLServiceTypeFacebook)
{
let post = SLComposeViewController(forServiceType: SLServiceTypeFacebook)!
post.setInitialText("First")
post.add(UIImage(named: "uround logo.png"))
self.present(post, animated: true, completion: nil)
} else {self.showAlert(service: "Facebook")}
}
let actionThree = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
//Add action to action sheet
alert.addAction(actionOne)
alert.addAction(actionThree)
//Present alert
self.present(alert, animated: true, completion: nil)
}
func showAlert(service:String)
{
let alert = UIAlertController(title: "Error", message: "You are not connected to \(service)", preferredStyle: .alert)
let action = UIAlertAction(title: "Dismiss", style: .cancel, handler: nil)
alert.addAction(action)
present(alert, animated: true, completion: nil)
}
}
extension CourseClass2: CLLocationManagerDelegate {
func determineMyCurrentLocation() {
locationManager = CLLocationManager()
locationManager?.delegate = self
locationManager?.desiredAccuracy = kCLLocationAccuracyBest
locationManager?.requestWhenInUseAuthorization()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let userLocation:CLLocation = locations[0] as CLLocation
manager.stopUpdatingLocation()
print("user latitude = \(userLocation.coordinate.latitude)")
print("user longitude = \(userLocation.coordinate.longitude)")
didReceiveUserLocation(userLocation)
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Error \(error)")
errorGettingCurrentLocation(error.localizedDescription)
}
public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse || status == .authorizedAlways {
locationManager?.startUpdatingLocation()
//locationManager.startUpdatingHeading()
} else if status == .denied || status == .restricted {
errorGettingCurrentLocation("Location access denied")
}
}
func errorGettingCurrentLocation(_ errorMessage:String) {
let alert = UIAlertController.init(title: "Error", message: errorMessage, preferredStyle: .alert)
alert.addAction(UIAlertAction.init(title: "Cancel", style: .cancel, handler: nil))
present(alert, animated: true, completion: nil)
}
}
adding a breakpoint i saw that the crush is in this func:
func insertRowMode3(ind:Int) {
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)
}
}
exactly at this line:
tableView.insertRows(at: [indPath], with: .right)
i don't know how i have to modify these functions to solve the problem, i already looked around to other questions but not find something useful.
other classes that work with this UIViewController are:
struct QCategoryy {
var name:String
init(name:String) {
self.name = name
}
}
extension QCategoryy: ExpressibleByStringLiteral {
init(stringLiteral value: String) {
self.name = value
}
init(unicodeScalarLiteral value: String) {
self.init(name: value)
}
init(extendedGraphemeClusterLiteral value: String) {
self.init(name: value)
}
}
and
import UIKit
import CoreLocation
private let geometryKey = "geometry"
private let locationKey = "location"
private let latitudeKey = "lat"
private let longitudeKey = "lng"
private let nameKey = "name"
private let openingHoursKey = "opening_hours"
private let openNowKey = "open_now"
private let vicinityKey = "vicinity"
private let typesKey = "types"
private let photosKey = "photos"
class QPlace: NSObject {
var location: CLLocationCoordinate2D?
var name: String?
var photos: [QPhoto]?
var vicinity: String?
var isOpen: Bool?
var types: [String]?
init(placeInfo:[String: Any]) {
// coordinates
if let g = placeInfo[geometryKey] as? [String:Any] {
if let l = g[locationKey] as? [String:Double] {
if let lat = l[latitudeKey], let lng = l[longitudeKey] {
location = CLLocationCoordinate2D.init(latitude: lat, longitude: lng)
}
}
}
// name
name = placeInfo[nameKey] as? String
// opening hours
if let oh = placeInfo[openingHoursKey] as? [String:Any] {
if let on = oh[openNowKey] as? Bool {
isOpen = on
}
}
// vicinity
vicinity = placeInfo[vicinityKey] as? String
// types
types = placeInfo[typesKey] as? [String]
// photos
photos = [QPhoto]()
if let ps = placeInfo[photosKey] as? [[String:Any]] {
for p in ps {
photos?.append(QPhoto.init(photoInfo: p))
}
}
}
func getDescription() -> String {
var s : [String] = []
if let name = name {
s.append("Name: \(name)")
}
if let vicinity = vicinity {
s.append("Vicinity: \(vicinity)")
}
if let types = types {
s.append("Types: \(types.joined(separator: ", "))")
}
if let isOpen = isOpen {
s.append(isOpen ? "OPEN NOW" : "CLOSED NOW")
}
return s.joined(separator: "\n")
}
func heightForComment(_ font: UIFont, width: CGFloat) -> CGFloat {
let desc = getDescription()
let rect = NSString(string: desc).boundingRect(with: CGSize(width: width, height: CGFloat(MAXFLOAT)), options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
return ceil(rect.height)
}
}
You have to first update your UITableViewDataSource, and then insert rows.
The order of business when inserting rows:
1. Make sure numberOfRowsInSection returns new, increased value
2. Make sure cellForRowAtIndexPath can provide the data for the cell at new index path
3. Actually insert rows in table view
Related
Here is my code :
class SchedulerViewController: UIViewController, UITableViewDelegate, UITableViewDataSource,
scheduleCellDelegate
{
var scheduleArray : Array<Array<String>>?
var scheduler : [String : Array<Array<String>>]?
var deviceID : String = ""
let retrievedString = KeychainWrapper.standard.string(forKey: "token")
var day = ""
var dayNum = 0
#IBOutlet weak var spinner: UIActivityIndicatorView!
#IBOutlet var buttons: [UIButton]!
#IBOutlet weak var scheduleView: UITableView!
var header : HTTPHeaders? = nil
var ScheduleURL : Dictionary<String, String>?
override func viewDidLoad() {
super.viewDidLoad()
scheduleView.delegate = self
scheduleView.dataSource = self
spinner.isHidden = true
//scheduleView.allowsSelection = false
scheduleView.register(UINib(nibName: "schedulerCell", bundle: nil), forCellReuseIdentifier: "schedulerCell")
self.getData()
}
func getData(){
AFFunctions.getAFRequest(ofType: ScheduleResponse.self, url: ScheduleURL!["GET"]!) { responseData, statusCode in
print(responseData?.data?.scheduler, statusCode)
self.scheduler = responseData?.data?.scheduler
DispatchQueue.main.async {
self.scheduleView.reloadData()
}
}
}
var buttonNum : Int?
#IBAction func daySelected(_ sender: UIButton) {
self.buttons.forEach { $0.tintColor = ($0 == sender) ? UIColor.orange : UIColor.systemTeal }
self.dayNum = sender.tag
switch dayNum {
case 0 : self.day = "Sunday"
case 1 : self.day = "Monday"
case 2 : self.day = "Tuesday"
case 3 : self.day = "Wednesday"
case 4 : self.day = "Thursday"
case 5 : self.day = "Friday"
case 6 : self.day = "Saturday"
default : self.day = "Sunday"
}
showDetail(day : day,dayNum : dayNum)
}
func showDetail(day : String, dayNum : Int) {
if let dayArray = scheduler?[day]
{
scheduleArray = dayArray
self.scheduleView.reloadData()
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return scheduleArray?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = Bundle.main.loadNibNamed("scheduleCell", owner: self, options: nil)?.first as! scheduleCell
cell.cellDelegate = self
cell.editBtn.tag = indexPath.row
cell.deleteSchedule.tag = indexPath.row
scheduleArray = scheduler![self.day]
/////////////THE BELOW STATEMENT THROWS THE ERROR ///////////////////
if let firstLabel = self.scheduleArray?[indexPath.row][0], let secondLabel = self.scheduleArray?[indexPath.row][1] {
DispatchQueue.main.async {
cell.timeLabel1.text = firstLabel
cell.timeLabel2.text = secondLabel
}
}
return cell
}
func didPressButton(_ tag: Int, btnType: String) {
let deleteURL = K.delURL
if(btnType == "delete") {
AFFunctions.deleteAFRequest(ofType: scheduleResponse.self, url: "\(deleteURL)?day=\(self.day)&place=\(tag)") { [self]
responseData, statusCode in
if(statusCode == 200){
let deleteAlert = UIAlertController(title: "Deleted", message: "Device Schedule Successfully Deleted", preferredStyle: UIAlertController.Style.alert)
deleteAlert.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (action: UIAlertAction!) in
self.scheduler![self.day]?.remove(at: tag)
self.scheduleView.reloadData()
}))
self.present(deleteAlert, animated: true, completion: nil)
}
}
}
}
}
I am doing the changes in the array locally as data is fetched only once in ViewDidLoad() with getData function. It shows a schedule for each day of the week (7 buttons, one for each day, are linked to an Outlet Collection), with 2 buttons embedded in the custom cell nib - an edit button and a delete button. I have implemented the logic for deleting, button tags are equal to IndexPath.row which works perfectly and I am able to delete the values I want but when I can't seem to get the table reload working. Even after deleting the row data, the table doesn't update itself. I am calling reloadData after successful deletion. What am I doing wrong?
there are 2 issues
when you update something on UI after a request call, you have to push the UI update process back to the main thread (tableview.reloadData() should get triggered on the main thread)
self.scheduleArray?[indexPath.row][0] and self.scheduleArray?[indexPath.row][1] is the issue index out of range. Because you assume it always contains 2 items without safe check.
I've refactored the code a bit as below
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "scheduleCell") else {
return Bundle.main.loadNibNamed("scheduleCell", owner: self, options: nil)?.first as! scheduleCell
}
cell.cellDelegate = self
cell.editBtn.tag = indexPath.row
cell.deleteSchedule.tag = indexPath.row
scheduleArray = scheduler?[self.day]
guard let item = scheduleArray?[indexPath.row], item.count == 2 else {
return cell
}
if let firstLabel = item.first, let secondLabel = item.last {
cell.timeLabel1.text = firstLabel
cell.timeLabel2.text = secondLabel
}
return cell
}
func didPressButton(_ tag: Int, btnType: String) {
let deleteURL = K.delURL
if(btnType == "delete") {
AFFunctions.deleteAFRequest(ofType: scheduleResponse.self, url: "\(deleteURL)?day=\(self.day)&place=\(tag)") { [self]
responseData, statusCode in
if(statusCode == 200){
// This has to be executed on the main thread to get tableView updated
DispatchQueue.main.async {
let deleteAlert = UIAlertController(title: "Deleted", message: "Device Schedule Successfully Deleted", preferredStyle: UIAlertController.Style.alert)
deleteAlert.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (action: UIAlertAction!) in
self.scheduler![self.day]?.remove(at: tag)
self.scheduleView.reloadData()
}))
self.present(deleteAlert, animated: true, completion: nil)
}
}
}
}
}
Solved it! Updated Code :
class SchedulerViewController: UIViewController, UITableViewDelegate, UITableViewDataSource,
scheduleCellDelegate
{
var scheduleArray : Array<Array<String>>?
var scheduler : [String : Array<Array<String>>]?
var deviceID : String = ""
let retrievedString = KeychainWrapper.standard.string(forKey: "token")
var day = ""
var dayNum = 0
#IBOutlet weak var spinner: UIActivityIndicatorView!
#IBOutlet var buttons: [UIButton]!
#IBOutlet weak var scheduleView: UITableView!
var header : HTTPHeaders? = nil
var ScheduleURL : Dictionary<String, String>?
override func viewDidLoad() {
super.viewDidLoad()
scheduleView.delegate = self
scheduleView.dataSource = self
spinner.isHidden = true
//scheduleView.allowsSelection = false
scheduleView.register(UINib(nibName: "schedulerCell", bundle: nil), forCellReuseIdentifier: "schedulerCell")
self.getData()
}
func getData(){
self.header =
[
"Content-Type" : "application/json",
"Authorization": retrievedString!
]
//scheduleView.register(scheduleCell.self, forCellReuseIdentifier: "scheduleCell")
print(ScheduleURL!["GET"])
AFFunctions.getAFRequest(ofType: ScheduleResponse.self, url: ScheduleURL!["GET"]!) { responseData, statusCode in
print(responseData?.data?.scheduler, statusCode)
self.scheduler = responseData?.data?.scheduler
self.scheduleView.reloadData()
}
}
var buttonNum : Int?
#IBAction func daySelected(_ sender: UIButton) {
self.buttons.forEach { $0.tintColor = ($0 == sender) ? UIColor.orange : UIColor.systemTeal }
self.dayNum = sender.tag
print(sender.tag)
switch dayNum {
case 0 : self.day = "Sunday"
case 1 : self.day = "Monday"
case 2 : self.day = "Tuesday"
case 3 : self.day = "Wednesday"
case 4 : self.day = "Thursday"
case 5 : self.day = "Friday"
case 6 : self.day = "Saturday"
default : self.day = "Sunday"
}
print(day, dayNum)
showDetail(day : day)
}
func showDetail(day : String) {
print(day)
print(scheduler?[day])
if let dayArray = scheduler?[day]
{ print(dayArray)
scheduleArray = dayArray
self.scheduleView.reloadData()
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("111 CHECK")
return scheduleArray?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = Bundle.main.loadNibNamed("scheduleCell", owner: self, options: nil)?.first as! scheduleCell
cell.cellDelegate = self
cell.editBtn.tag = indexPath.row
cell.deleteSchedule.tag = indexPath.row
if let item = scheduleArray?[indexPath.row],
item.count > 1 {
DispatchQueue.main.async {
cell.timeLabel1.text = item[0]
cell.timeLabel2.text = item[1]
}
}
return cell
}
func didPressButton(_ tag: Int, btnType: String) {
let deleteURL = K.delURL
print("134", self.day)
print("135", self.scheduleArray?[tag])
print("136", scheduler?[self.day])
print("TAG : ", tag)
print("BTN TYPE: ", btnType)
if(btnType == "delete") {
AFFunctions.deleteAFRequest(ofType: scheduleResponse.self, url: "\(deleteURL)?day=\(self.day)&place=\(tag)") { [self]
responseData, statusCode in
print("\(deleteURL)?day=\(self.day)&place=\(tag)")
print(responseData, statusCode)
if(statusCode == 200){
self.scheduler![self.day]!.remove(at: tag)
DispatchQueue.main.async {
let deleteAlert = UIAlertController(title: "Deleted", message: "Device Schedule Successfully Deleted", preferredStyle: UIAlertController.Style.alert)
deleteAlert.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (action: UIAlertAction!) in
self.showDetail(day: self.day)
// self.scheduleView.reloadData()
}))
self.present(deleteAlert, animated: true, completion: nil)
}
}
}
}
}
}
The issue here is you are holding multiple sources of truth and fail at synchronysing them. You have:
var scheduleArray : Array<Array<String>>?
var scheduler : [String : Array<Array<String>>]?
Where both seem to hold the same information in different form. I can´t see why you are doing this from the example code you posted.
You get an error at:
self.scheduleArray?[indexPath.row][0]
because when you delete your item you are removing it from scheduler and reload your tableview. The tableview on the other hand get´s the information how many rows it should render from scheduleArray:
return scheduleArray?.count ?? 0
and these differ at that time because you didn´t assign scheduler to scheduleArray.
So 2 possible solutions here:
assign scheduleArray before you reload your tableview
self.scheduler![self.day]?.remove(at: tag)
scheduleArray = scheduler![self.day]
and remove the assignment in the cellForItemAt function
stop using scheduleArray and scheduler. Use only a single collection to hold the information.
I have tableview listed pdfs by retrieving from Firebase. No problem with retrieving datas and sending default SMTP. What I am trying to do that selected urls(pdf) in tableview, will be sent by using SMTP to user email so that user can easily reach pdf urls. I could not figure out how to handle it. This is the only critical point in my project. Any help is appreciated.
import UIKit
import Firebase
import PDFKit
import skpsmtpmessage
class IcsViewcontroller: UIViewController , UISearchBarDelegate,SKPSMTPMessageDelegate{
var preImage : UIImage?
let cellSpacingHeight: CGFloat = 20
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var pdfListView: UITableView!
#IBOutlet weak var spinner: UIActivityIndicatorView!
var pdfList = [pdfClass]()
var searchall = [pdfClass]()
var searching = false
var selectedPdf = [pdfClass]()
override func viewDidLoad() {
super.viewDidLoad()
let editButton = UIBarButtonItem(title: "Edit", style: .plain, target: self, action: #selector(showEditing(_:)))
navigationItem.rightBarButtonItem = editButton
pdfListView.delegate = self
pdfListView.dataSource = self
searchBar.delegate = self
self.pdfListView.isHidden = true
getPdf()
sendEmail()
searchBar.searchTextField.backgroundColor = .white
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let selectionIndexPath = self.pdfListView.indexPathForSelectedRow {
self.pdfListView.deselectRow(at: selectionIndexPath, animated: animated)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if searching {
let destination = segue.destination as! PdfKitViewController
let selectedIndexPath = pdfListView.indexPathForSelectedRow
destination.pdf = searchall[selectedIndexPath!.row]
} else {
let destination = segue.destination as! PdfKitViewController
let selectedIndexPath = pdfListView.indexPathForSelectedRow
destination.pdf = pdfList [selectedIndexPath!.row]
}
}
#objc func showEditing(_ sender: UIBarButtonItem)
{
if(self.pdfListView.isEditing == true)
{
self.pdfListView.isEditing = false
self.navigationItem.rightBarButtonItem?.title = "Edit"
}
else
{
self.pdfListView.allowsMultipleSelectionDuringEditing = true
self.pdfListView.isEditing = true
self.navigationItem.rightBarButtonItem?.title = "Done"
}
}
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
if searchBar.text == nil || searchBar.text == "" {
searching = false
} else {
searching = true
}
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searching = false
searchBar.text = ""
self.pdfListView.reloadData()
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
searching = false
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar.text == nil || searchBar.text == "" {
searchall = pdfList
searching = false
pdfListView.reloadData()
} else {
searching = true
searchall = pdfList.filter({($0.pdf_name?.lowercased().prefix(searchText.count))! == searchText.lowercased() })
pdfListView.reloadData()
}
}
func getPdf () {
spinner.startAnimating()
let docRef = Storage.storage().reference().child("ICS_Documents")
docRef.listAll{ (result , error ) in
if error != nil {
let alert = UIAlertController(title: "Error", message: "No Database Connection", preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil))
self.present(alert, animated: true, completion: nil)
self.spinner.stopAnimating()
}
for item in result.items {
let storeageLocation = String( describing : item)
let gsReference = Storage.storage().reference(forURL: storeageLocation)
gsReference.downloadURL{ url , error in
if let error = error{
print(error)
} else {
let pdf_name = String( item.name)
let pdf_url = url?.absoluteString
let thumbnailSize = CGSize(width: 100, height: 100)
let thmbnail = self.generatePdfThumbnail(of: thumbnailSize, for: url!, atPage: 0)
let pdfall = pdfClass(pdf_name: pdf_name, pdf_url: pdf_url!, pdf_preview: thmbnail!)
self.pdfList.append(pdfall)
}
DispatchQueue.main.async {
self.pdfList.sort{ $0.pdf_name ?? "" < $1.pdf_name ?? ""}
self.pdfListView.reloadData()
self.spinner.stopAnimating()
self.pdfListView.isHidden = false
}
}
}
}
}
func generatePdfThumbnail(of thumbnailSize: CGSize , for documentUrl: URL, atPage
pageIndex: Int) -> UIImage? {
let pdfDocument = PDFDocument(url: documentUrl)
let pdfDocumentPage = pdfDocument?.page(at: pageIndex)
return pdfDocumentPage?.thumbnail(of: thumbnailSize, for:
PDFDisplayBox.trimBox)
}
func sendEmail() {
let message = SKPSMTPMessage()
message.relayHost = "smtp.gmail.com"
message.login = "xxx#gmail.com"
message.pass = "******"
message.requiresAuth = true
message.wantsSecure = true
message.relayPorts = [587]
message.fromEmail = "xxxx#gmail.com"
message.toEmail = "xxxx#gmail.com"
message.subject = "subject"
let messagePart = [kSKPSMTPPartContentTypeKey: "text/plain; charset=UTF-8",
kSKPSMTPPartMessageKey: "Hi alll"]
message.parts = [messagePart]
message.delegate = self
message.send()
}
func messageSent(_ message: SKPSMTPMessage!) {
print("Successfully sent email!")
}
func messageFailed(_ message: SKPSMTPMessage!, error: Error!) {
print("Sending email failed!")
}
}
extension IcsViewcontroller : UITableViewDelegate,UITableViewDataSource{
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
if searching{
return searchall.count
}else {
return pdfList.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "pdfCell", for: indexPath) as! pdfCellTableViewCell
let varcell : pdfClass
if searching {
varcell = searchall [indexPath.row]
} else {
varcell = pdfList [indexPath.row]
}
cell.configure(name: varcell.pdf_name! , pdfthumbnail: varcell.pdf_preview!)
cell.accessoryType = .disclosureIndicator
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
var indx : pdfClass
if searching{
indx = searchall[indexPath.row ]
}else {
indx = pdfList[indexPath.row]
}
self.selectdeselectcell(tableview: tableView, indexpath: indexPath)
print("selected")
if pdfListView.isEditing {
func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
return !tableView.isEditing
}
}else{
performSegue(withIdentifier: "toPdfKit", sender: indx)
print(indexPath.row)
}
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
self.selectdeselectcell(tableview: tableView, indexpath: indexPath)
print("deselected")
}
func selectdeselectcell(tableview : UITableView ,indexpath : IndexPath){
if pdfListView.isEditing{
self.selectedPdf.removeAll()
if let arr = pdfListView.indexPathForSelectedRow{
print(arr)
}
}else {
return
}
}
/* func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let deleteAction = UITableViewRowAction(style: .default, title: "Delete", handler: { (action:UITableViewRowAction,indexPath:IndexPath)-> Void in
let storage = Storage.storage()
var childsURL : String
if self.searching {
childsURL = self.searchall[indexPath.row ].pdf_url!
}else{
childsURL = self.pdfList[indexPath.row].pdf_url!
}
let storageref = storage.reference(forURL: childsURL)
storageref.delete{ error in
if let error = error {
print(error.localizedDescription)
} else{
print("File deleted")
}
}
self.pdfList.removeAll()
self.getPdf()
})
return [deleteAction]
}*/
}
I get
Referencing instance method 'encode' on 'Array' requires that '(item: String?, price: String?, salesPrice: String?)' conform to 'Encodable' "
this error in the storeData() function. Did I even save the tuple correctly in user defaults? If anyone can help that would be great!Any help is appreciated!
import UIKit
let defaults = UserDefaults(suiteName: "com.Saving.Data")
struct Product: Codable {
var title: String
var price: String
var salePrice: String
}
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var items: [(item: String?, price: String?, salesPrice: String?)] = []
override func viewDidLoad() {
super.viewDidLoad()
getData()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
getData()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(true)
storeData()
}
override var prefersStatusBarHidden: Bool {
return true
}
#IBAction func addButtonTapped(_ sender: Any) {
let alert = UIAlertController(title: "Product Information", message: nil, preferredStyle: .alert)
alert.addTextField { (itemTF) in
itemTF.placeholder = "Item"
}
alert.addTextField { (textField) in
textField.placeholder = "Price"
}
alert.addTextField { (textField) in
textField.placeholder = "Sale Price"
}
let action = UIAlertAction(title: "Add", style: .default) { (_) in
var product : (item: String, price: String, salesPrice: String) = ("","","")
if let textField1 = alert.textFields?[0], let text = textField1.text {
print(text)
product.item = text
}
if let textField2 = alert.textFields?[1], let text = textField2.text {
print(text)
product.price = text
}
if let textField3 = alert.textFields?[2], let text = textField3.text {
print(text)
product.salesPrice = text
}
self.add(product)
}
alert.addAction(action)
present(alert, animated: true)
storeData()
}
func add(_ product: (item: String, price: String, salesPrice: String)) {
let index = 0
items.insert(product, at: index)
let indexPath = IndexPath(row: index, section: 0)
tableView.insertRows(at: [indexPath], with: .left)
storeData()
}
func storeData() {
if let data = try? PropertyListEncoder().encode(items) {
UserDefaults.standard.set(data, forKey: "savedData")
}
}
func getData() {
if let data = UserDefaults.standard.data(forKey: "savedData") {
let items = try! PropertyListDecoder().decode([Product].self, from: data)
print(items)
}
}
}
extension ViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
let product = items[indexPath.row]
cell.textLabel?.text = product.item
print(product.price ?? "")
print(product.salesPrice ?? "")
cell.contentView.backgroundColor = UIColor(red:0.92, green:0.92, blue:0.92, alpha:1.0)
cell.textLabel?.textColor = UIColor(red:0.13, green:0.13, blue:0.13, alpha:1.0)
tableView.separatorColor = UIColor(red:0.92, green:0.92, blue:0.92, alpha:1.0)
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
guard editingStyle == .delete else { return }
items.remove(at: indexPath.row)
tableView.reloadData()
storeData()
}
}
As already mentioned in the comments it's incomprehensible why you use an extra tuple with the same design as your struct Product. This causes your problems.
So get rid of the tuple.
Declare items
var items = [Product]()
Replace the alert action with
let action = UIAlertAction(title: "Add", style: .default) { _ in
let item = alert.textFields?[0].text ?? ""
let price = alert.textFields?[1].text ?? ""
let salesPrice = alert.textFields?[2].text ?? ""
let product = Product(item: item, price: price, salesPrice: salesPrice)
self.addProduct(product)
}
Replace add with
func addProduct(_ product: Product) {
let index = 0
items.insert(product, at: index)
let indexPath = IndexPath(row: index, section: 0)
tableView.insertRows(at: [indexPath], with: .left)
storeData()
}
Replace getData with
func getData() {
if let data = UserDefaults.standard.data(forKey: "savedData") {
do {
items = try PropertyListDecoder().decode([Product].self, from: data)
print(items)
tableView.reloadData()
} catch { print(error) }
}
}
Side note: Be aware that the code saves the data in standard UserDefaults, not in your custom suite.
And finally – not related to the issue – to get a nice animation in tableView:commit editingStyle: replace
tableView.reloadData()
with
tableView.deleteRows(at: [indexPath], with: .fade)
How can I update or insert a row in a tableview without reloading all the data?
In the ClientsViewController a list of clients is shown alphabetically and separated by sections (first letter of the client`s name).
When I update a client, the tableview shows 2 entries (old and new one) of the same client. When I try to add a client, it crashes.
I think the problem is with indexing.
class ClientsViewController: UITableViewController {
var sortedFirstLetters: [String] = []
var sections: [[Client]] = [[]]
var tableArray = [Client]()
var client: Client?
var refresher: UIRefreshControl!
#IBOutlet var noClientsView: UIView!
#IBAction func unwindToClients(sender: UIStoryboardSegue) {
if let sourceViewController = sender.source as? ClientViewController, let client = sourceViewController.client {
if let selectedIndexPath = tableView.indexPathForSelectedRow {
// Update an existing client.
tableArray[selectedIndexPath.row] = client
tableView.reloadRows(at: [selectedIndexPath], with: .automatic)
}
else {
// Add a client.
let newIndexPath = IndexPath(row: tableArray.count, section: 0)
tableArray.append(client)
tableView.insertRows(at: [newIndexPath], with: .automatic)
}
let firstLetters = self.tableArray.map { $0.nameFirstLetter }
let uniqueFirstLetters = Array(Set(firstLetters))
self.sortedFirstLetters = uniqueFirstLetters.sorted()
self.sections = self.sortedFirstLetters.map { firstLetter in
return self.tableArray
.filter { $0.nameFirstLetter == firstLetter }
.sorted { $0.name < $1.name }
}
// DispatchQueue.main.async {
// self.tableView.reloadData()
// }
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let secondScene = segue.destination as! ClientViewController
if segue.identifier == "ShowDetail", let indexPath = self.tableView.indexPathForSelectedRow {
let currentPhoto = sections[indexPath.section][indexPath.row]
secondScene.client = currentPhoto
}
else if segue.identifier == "AddItem" {
print("add")
}
else {
fatalError("The selected cell is not being displayed by the table")
}
}
#objc func handleRefresh(_ refreshControl: UIRefreshControl) {
getClients()
refreshControl.endRefreshing()
}
}
extension ClientsViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.refreshControl?.addTarget(self, action: #selector(ClientsViewController.handleRefresh(_:)), for: UIControlEvents.valueChanged)
tableView.backgroundView = noClientsView
getClients() //for only the 1st time ==> when view is created ==> ok ish
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
getClients() // not a good idea to make a request to the server everytime the view appears on the screen.
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if(self.tableArray.count > 0) {
return sortedFirstLetters[section]
}
else {
return ""
}
}
override func sectionIndexTitles(for tableView: UITableView) -> [String]? {
print(sortedFirstLetters)
return sortedFirstLetters
}
override func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let name = sections[indexPath.section][indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "ClientCell", for: indexPath)
cell.textLabel?.text = name.name
cell.detailTextLabel?.text = name.city + " - " + name.province
return cell
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if(sections[section].count > 0) {
tableView.backgroundView = nil
}
return sections[section].count
}
func getClients() {
makeRequest(endpoint: "api/clients/all",
parameters: [:],
completionHandler: { (container : ApiContainer<Client>?, error : Error?) in
if let error = error {
print("error calling POST on /getClients")
print(error)
return
}
self.tableArray = (container?.result)!
self.prepareData()
DispatchQueue.main.async {
self.tableView.reloadData()
}
} )
}
//sorts and makes the index
func prepareData() {
let firstLetters = self.tableArray.map { $0.nameFirstLetter }
let uniqueFirstLetters = Array(Set(firstLetters))
self.sortedFirstLetters = uniqueFirstLetters.sorted()
self.sections = self.sortedFirstLetters.map { firstLetter in
return self.tableArray
.filter { $0.nameFirstLetter == firstLetter }
.sorted { $0.name < $1.name }
}
}
}
Bellow is ClientViewController and Client struct.
class ClientViewController: UITableViewController, UIPickerViewDelegate, UIPickerViewDataSource {
#IBOutlet weak var nameTextField: UITextField!
#IBOutlet weak var addressTextField: UITextField!
#IBOutlet weak var cityTextField: UITextField!
#IBOutlet weak var provinceTextField: UITextField!
#IBOutlet weak var postalCodeTextField: UITextField!
#IBOutlet weak var contactsLabel: UILabel!
let numberOfRowsAtSection: [Int] = [4, 2]
var client: Client?
var selectedProvince: String?
override func viewWillAppear(_ animated: Bool) {
self.title = "New"
if (client?.client_id) != nil {
self.title = "Edit"
nameTextField.text = client?.name
provinceTextField.text = client?.province
cityTextField.text = client?.city
addressTextField.text = client?.address
postalCodeTextField.text = client?.postal_code
selectedProvince = client?.province
}
}
#objc func save(sender: UIButton!) {
let name = nameTextField.text ?? ""
let address = addressTextField.text ?? ""
let city = cityTextField.text ?? ""
let province = selectedProvince ?? ""
let postal_code = postalCodeTextField.text ?? ""
var endPoint: String
if (client?.client_id) != nil {
endPoint = "api/clients/update"
} else {
endPoint = "api/clients/add"
}
client = Client(name:name, client_id: client?.client_id, postal_code: postal_code, province: province, city: city, address: address)
let requestBody = makeJSONData(client)
makeRequestPost(endpoint: endPoint,
requestType: "POST",
requestBody: requestBody,
view: view,
completionHandler: { (response : ApiContainer<Client>?, error : Error?) in
if let error = error {
print("error calling POST on /todos")
print(error)
return
}
let b = (response?.meta)!
let a = (response?.result[0])
let client_id = a?.client_id
self.client?.client_id = client_id
if(b.sucess == "yes") {
//change message and use the custom func like on error.
let alert = UIAlertController(title: "Success!", message: "All good", preferredStyle: .alert)
let OKAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {
(_)in
self.performSegue(withIdentifier: "unwindToClients", sender: self)
})
alert.addAction(OKAction)
DispatchQueue.main.async(execute: {
self.present(alert, animated: true, completion: nil)
})
}
else
{
self.showAlert(title: "Error", message: "Error Creating Client")
//return
}
} )
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.sectionHeaderHeight = 50.0;
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Save", style: .plain, target: self, action: #selector(save))
let thePicker = UIPickerView()
provinceTextField.inputView = thePicker
thePicker.delegate = self
// ToolBar
let toolBar = UIToolbar()
toolBar.barStyle = .default
toolBar.isTranslucent = true
toolBar.tintColor = UIColor(red: 92/255, green: 216/255, blue: 255/255, alpha: 1)
toolBar.sizeToFit()
// Adding Button ToolBar
let doneButton = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(ClientDetailViewController.doneClick))
let spaceButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let cancelButton = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(ClientDetailViewController.cancelClick))
toolBar.setItems([cancelButton, spaceButton, doneButton], animated: false)
toolBar.isUserInteractionEnabled = true
provinceTextField.inputAccessoryView = toolBar
}
#objc func doneClick() {
provinceTextField.resignFirstResponder()
}
#objc func cancelClick() {
provinceTextField.resignFirstResponder()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var rows: Int = 0
if section < numberOfRowsAtSection.count {
rows = numberOfRowsAtSection[section]
}
return rows
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let backItem = UIBarButtonItem()
backItem.title = "Client"
navigationItem.backBarButtonItem = backItem
if segue.identifier == "unwindToClients",
let destination = segue.destination as? ClientsViewController
{
destination.client = client
}
if segue.identifier == "showContacts",
let destination = segue.destination as? ContactsViewController
{
destination.client = client
}
}
// MARK: - Picker view
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView( _ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return provinces.count
}
func pickerView( _ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return provinces[row].name
}
func pickerView( _ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
provinceTextField.text = provinces[row].name
}
}
struct Client: Codable {
var client_id: Int!
let name: String!
let postal_code: String!
let province: String!
let city: String!
let address: String!
init(name: String, client_id: Int! = nil, postal_code: String, province: String, city: String, address: String) {
self.client_id = client_id
self.name = name
self.postal_code = postal_code
self.province = province
self.city = city
self.address = address
}
var nameFirstLetter: String {
return String(self.name[self.name.startIndex]).uppercased()
}
}
Append the items in datasource at index where you want to insert row in table. than follow steps 2,3,4.
Call method tbl.beginupdate
call insertRowsAtIndexPaths method of table view
Call tbl.endupdate
you can update tableView rows using this..
self.tableView.reloadRows(at: [indexPath!], with: .top)
I'm creating an app just to learn the language mostly. basically I am trying to make an app that allows you to bet fake money on which CSGO team will win. So right now I am at the part of the app that allows a user to create a bet. then the user can see all of the bets he can then accept another users bet. I had all this working before, but I noticed a bug so I decided to fix it. The problem is now it crashes.
I need the app to go to a different screen where it allows you to make a bet on a team. then you click the box and set how much you want to bet on that specific team. Then when you click the submit Button at the bottom, it will upload the users information and the bet information to a Firebase Database. then it will go back to the previous screen that shows all of the bets that have not been filled yet. so his should too.
the problem is when I use POPView controller to go back to the previous view controller that shows the bets it gives me an index out of range error for the table view. the weird thing is if I move the method getBetsFor to viewDidLoad instead of view will appear then it works just fine. The problem with that is it will glitch and it won't show the bet that the user just made.
I know my code is horrific I have tried to put some of it into MVC format, but without further ado here is my code.
the code below is the code that gets the bets from the Firebase Database then puts that data into an array of objects then displays the objects onto the TableView
//
// BetView.swift
// EDraft
//
// Created by Devin Tripp on 3/20/17.
// Copyright © 2017 Devin Tripp. All rights reserved.
//
import UIKit
import Foundation
import Firebase
class BetView: UIViewController, UITableViewDelegate, UITableViewDataSource{
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var teamOne: UILabel!
#IBOutlet weak var teamTwo: UILabel!
let datRef = FIRDatabase.database().reference(fromURL: "https://edraft-77b47.firebaseio.com/")
var userMoney = String()
let getData = GatherData()
var testies: String?
var teamTOne = String()
var teamTTwo = String()
let screenSize: CGRect = UIScreen.main.bounds
let navBar: UINavigationBar = UINavigationBar(frame: CGRect(x: 0, y: 60, width: UIScreen.main.bounds.width, height: 50))
var navItem = UINavigationItem(title: "Money: ")
let doneItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.add, target: nil, action: #selector(sayHello(sender:)))
let backItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.done, target: nil, action: #selector(goBack(sender:)))
override func viewWillAppear(_ animated: Bool) {
self.getData.betObjects.removeAll()
self.getData.getBetsFor(completion: { (result) in
if result == true {
//show the spinning wheel thing
self.tableView.reloadData()
} else {
// error
}
})
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(navBar)
self.tableView.separatorStyle = .none
navItem.rightBarButtonItem = doneItem
navItem.leftBarButtonItem = backItem
navBar.setItems([navItem], animated: false)
teamOne.text = teamTOne
teamTwo.text = teamTTwo
/*
getData.getBetsFor(completion: { (result) in
if result == true {
self.tableView.reloadData()
} else {
}
})
*/
getData.getMoneyFromUser(username: self.getData.userName, completion: { (money) in
if money == true {
let usermoney = String(self.getData.userMoney)
//update the UI
self.navItem.title = "$" + usermoney
print(self.getData.userMoney)
} else {
print("no money found")
}
})
}
func tableView(_ tableView: UITableView, shouldUpdateFocusIn context: UITableViewFocusUpdateContext) -> Bool {
return true
}
func updateBet(_ index: Int, completion: #escaping (_ something: Bool) -> Void) {
let userID = FIRAuth.auth()?.currentUser?.uid
datRef.child("User").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let username = value?["username"] as? String
self.getData.userName = username!
// ...
self.datRef.child("Bets").observe(.childAdded, with: { snapshot in
//
// this is the unique identifier of the bet. eg, -Kfx81GvUxoHpmmMwJ9P
guard let dict = snapshot.value as? [String: AnyHashable] else {
print("failed to get dictionary from Bets.\(self.getData.userName)")
return
}
let values = ["OpposingUsername": self.getData.userName,"Show": "no"]
//var val = self.getData.betObjects[index]
//val.opposingUserName = self.getData.userName
self.datRef.child("Bets").child(self.getData.tieBetToUser[index]).observeSingleEvent(of: .value, with: { (snapshot) in
let anothaValue = snapshot.value as? NSDictionary
let user = anothaValue?["Username"] as? String
if user == self.getData.userName {
// let the user know he cannot bet his own bet
completion(false)
} else {
self.datRef.child("Bets").child(self.getData.tieBetToUser[index]).updateChildValues(values)
completion(true)
}
})
})
}) { (error) in
print(error.localizedDescription)
}
}
func getOpoosingUserNames(_ username: String,_ index: Int, completion: #escaping (_ result: Bool) -> Void ) {
let userID = FIRAuth.auth()?.currentUser?.uid
datRef.child("User").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let username = value?["username"] as? String ?? ""
self.datRef.child("Bets").child(self.getData.tieBetToUser[index]).observeSingleEvent(of: .value, with: { snapshot in
let thisValue = snapshot.value as? NSDictionary
if let thisUserName = thisValue?["Username"] as? String {
self.getData.opposingUserName = thisUserName
completion(true)
} else {
completion(false)
}
})
}) { (error) in
print(error.localizedDescription)
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//if makeEmpty == true {
// return 0
//} else {
print(self.getData.betObjects.count)
return self.getData.betObjects.count
//}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "cellB", for: indexPath) as! CellB
print(self.getData.betObjects[indexPath.row].userName)
cell.userNameLabel?.text = getData.betObjects[indexPath.row].userName
cell.amountOfBet?.text = getData.betObjects[indexPath.row].betAmount
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let alertController = UIAlertController(title: "Accept Bet", message: "Match the bet of " + getData.amountBets[indexPath.row], preferredStyle: .alert)
let okButton = UIAlertAction(title: "No", style: .default, handler: { (action) -> Void in
print("Ok button tapped")
})
let yesButton = UIAlertAction(title: "Yes", style: .default, handler: { (action) -> Void in
// let them know to wait a second or the bet won't go through
var waitController = UIAlertController(title: "Please Wait", message: "Your bet is being processed", preferredStyle: .alert)
self.present(waitController, animated: true, completion: nil)
//take away the usersMoney
self.takeAwayMoney(self.getData.amountBets[indexPath.row],index: indexPath.row, completion: { (result) in
if result == true {
self.updateBet(indexPath.row, completion: { (result) in
if result == true {
self.getOpoosingUserNames(self.getData.userName, indexPath.row, completion: { (anothaResult) in
if anothaResult == true {
self.dismiss(animated: true, completion: {
let successController = UIAlertController(title: "Success", message: "You have made a bet with " + self.getData.opposingUserName, preferredStyle: .alert)
let okButt = UIAlertAction(title: "Ok", style: .default, handler: nil)
successController.addAction(okButt)
self.present(successController, animated: true, completion: nil)
//lastly delete the opposing UserName
let pathIndex = IndexPath(item: indexPath.row, section: 0)
self.getData.betObjects.remove(at: indexPath.row)
self.tableView.deleteRows(at: [pathIndex], with: .fade)
self.tableView.reloadData()
print("Second")
})
self.getData.getMoneyFromUser(username: self.getData.userName, completion: { (money) in
if money == true {
self.userMoney = String(self.getData.userMoney)
//update the UI
self.navItem.title = "$" + self.userMoney
print(self.userMoney)
} else {
print("no money found")
}
})
} else {
}
//wait for the first view to load in case it uploads to fast
})
} else {
self.dismiss(animated: true, completion: {
let cannotBet = UIAlertController(title: "Failed", message: "You cannot bet with yourself dummy", preferredStyle: .alert)
let okButt = UIAlertAction(title: "Ok", style: .default, handler: nil)
cannotBet.addAction(okButt)
self.present(cannotBet, animated: true, completion: nil)
})
}
})
} else {
// user doesn't have money
//display a alert that lets the user know hes broke
print("this should print once")
self.dismiss(animated: true, completion: {
let brokeController = UIAlertController(title: "Failed", message: "Reason: You don't have enough money!", preferredStyle: .alert)
let okButt = UIAlertAction(title: "Ok", style: .default, handler: nil)
brokeController.addAction(okButt)
self.present(brokeController, animated: true, completion: nil)
})
}
var getResult = ""
print("You have taken away the users money")
print("you made it this far almost there")
//let delayInSeconds = 3.0 // 1
//DispatchQueue.main.asyncAfter(deadline: .now() + delayInSeconds) { // 2
})
return
})
alertController.addAction(okButton)
alertController.addAction(yesButton)
present(alertController, animated: true, completion: nil)
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if(editingStyle == UITableViewCellEditingStyle.delete) {
self.getData.betObjects.remove(at: indexPath.row)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//let indexPath = self.tableView.indexPathForSelectedRow!
//var DestViewController : addBets = segue.destination as! addBets
//DestViewController.teamOne = self.teamOne.text!
//DestViewController.teamOne = self.teamTTwo
if segue.identifier == "gotobetview" {
if let destination = segue.destination as? addBets {
destination.teamOne = self.teamOne.text!
destination.teamTwo = self.teamTwo.text!
}
}
}
func takeAwayMoney(_ howMuch: String, index: Int, completion: #escaping (Bool)-> ()) -> Void{
if let notMuch = Int(howMuch) {
let userID = FIRAuth.auth()?.currentUser?.uid
datRef.child("User").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let money = value?["money"] as? String ?? ""
//convert money to int
if let conMoney = Int(money) {
var conMoreMoney = conMoney
if conMoreMoney < notMuch {
print(" You don't have enough money")
completion(false)
return
} else {
conMoreMoney -= notMuch
let values = ["money": String(conMoreMoney)]
//update the users money
self.datRef.child("User").child(userID!).updateChildValues(values)
completion(true)
}
}
// ...
}) { (error) in
print(error.localizedDescription)
}
}
}
func getUserName() -> String {
let userID = FIRAuth.auth()?.currentUser?.uid
var useruser: String!
datRef.child("User").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let username = value?["username"] as? String ?? ""
self.getData.userName = username
useruser = username
// ...
}) { (error) in
print(error.localizedDescription)
}
return useruser
}
func sayHello(sender: UIBarButtonItem) {
let userInput = self.teamOne.text
var userInputArray: [String] = []
userInputArray.append(self.teamOne.text!)
userInputArray.append(self.teamTwo.text!)
performSegue(withIdentifier: "gotobetview", sender: userInputArray)
//let storyboard = UIStoryboard(name: "Main", bundle: nil)
//let homeView = storyboard.instantiateViewController(withIdentifier: "addBets")
//self.present(homeView, animated: true, completion: nil)
}
func goBack(sender: UIBarButtonItem) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let backView = storyboard.instantiateViewController(withIdentifier: "tabview")
self.present(backView, animated: true, completion: nil)
}
}
Since I cannot post anymore code I will update the project on github and post a link here.
https://github.com/devintrippprojects/EDraft
if you download the project you can always create your own account you will start out with 100 dollars or you could use the main testing account the email is
k#k.com
and the password is
kkkkkk
I will post how the database looks and how the app looks tomorrow thanks have a good night.
I downloaded and ran your project.
Seems like the issue here is you should reload the tableView when you remove all the data source.
change this:
self.getData.betObjects.removeAll()
self.getData.getBetsFor(completion: { (result) in
if result == true {
//show the spinning wheel thing
self.tableView.reloadData()
} else {
// error
}
})
to this:
self.getData.betObjects.removeAll()
self.tableView.reloadData()
self.getData.getBetsFor(completion: { (result) in
if result == true {
//show the spinning wheel thing
self.tableView.reloadData()
} else {
// error
}
})
With an extra line:
self.tableView.reloadData()