can't retrieve indexPath on prepareforsegue? - swift

I'm having a problem with retrieving the indexPath on prepareForSegue with the "comments" block. The comments button is inside the cell, and I created a segue from the view controller to the desired vc but every time I click the button, I get an "unexpectedly found nil while unwrapping an optional value" on the part where I declare the indexPath.
override func shouldPerformSegueWithIdentifier(identifier: String?, sender: AnyObject?) -> Bool {
if (identifier == "webPage1") {
let indexPath: NSIndexPath = tableView.indexPathForSelectedRow()!
if (arrayByVotes[indexPath.row].objectForKey("videoURL") as NSString == "") {
performSegueWithIdentifier("comments", sender: nil)
return false
}
}
return true
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "webPage1") {
var urlToOpen:String = ""
let indexPath: NSIndexPath = tableView.indexPathForSelectedRow()!
if((arrayByVotes[indexPath.row].objectForKey("videoURL")) != nil) {
urlToOpen = arrayByVotes[indexPath.row].objectForKey("videoURL") as String
urlToOpen = urlToOpen.stringByReplacingOccurrencesOfString("http:", withString: "https:", options: .LiteralSearch, range: nil)
}
let sW: webpageViewController = segue.destinationViewController as webpageViewController
sW.urlPath = urlToOpen
} else if (segue.identifier == "comments") {
let indexPath: NSIndexPath = tableView.indexPathForSelectedRow()!
let sweet:PFObject = arrayByVotes[indexPath.row] as PFObject
var postId = sweet.objectId
let selectedFContent: String = arrayByVotes[indexPath.row].objectForKey("content") as String
let fpvc: FeedPageViewController = segue.destinationViewController as FeedPageViewController
fpvc.selectedFeedFeedContent = selectedFContent
fpvc.ourpostId = postId
}
}
I then tried wrapping it in an
if let indexPath = tableView.indexPathForSelectedRow()!
but that didn't work either as it wouldn't retrieve the correct indexPath every time.
Essentially the program is supposed to check to see if a cell contains a URL. If it does, it calls the vc with a webview, and if not, then it directly calls the comments page.

Try this
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "webPage1") {
let selectedRow = tableView.indexPathForSelectedRow()?.row
var urlToOpen = arrayByVotes(selectedRow!) as? String
if((urlToOpen != nil) {
urlToOpen = urlToOpen.stringByReplacingOccurrencesOfString("http:", withString: "https:", options: .LiteralSearch, range: nil)
}
let sW: webpageViewController = segue.destinationViewController as webpageViewController
sW.urlPath = urlToOpen
} else if (segue.identifier == "comments") {
let selectedRow = tableView.indexPathForSelectedRow()?.row
let sweet:PFObject = arrayByVotes(selectedRow!) as PFObject
var postId = sweet.objectId
let selectedFContent: String = arrayByVotes(selectedRow!).objectForKey("content") as String
let fpvc: FeedPageViewController = segue.destinationViewController as FeedPageViewController
fpvc.selectedFeedFeedContent = selectedFContent
fpvc.ourpostId = postId
}
}

Related

This class is not key value coding-compliant for the key ProductsCollection.'

I am getting this exception:
Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key ProductsCollection.'
I read previous posts about this, and I checked twice - and my UIVewCollection (ProductsCollection) is linked to the outlet.
My Class :
class SellProductsView: ProductsCollectionViewController
{
override func viewDidLoad()
{
// Do any additional setup after loading the view.
self.ProductsCollection.delegate = self
self.ProductsCollection.dataSource = self;
LoadProducts(productsToSellOrBuy: "ToSell")
super.viewDidLoad()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
if segue.identifier == "view_product_information"
{
let prodPageView = segue.destination as! SellProductPageView
PrepareProductForSegue(prodPageView: prodPageView)
}
}
}
My base class :
class ProductsCollectionViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource
{
#IBOutlet weak var ProductsCollection: UICollectionView!
var ref: DatabaseReference?
var databaseHandle: DatabaseHandle?
#IBAction func unwindFromProductPageView(segue: UIStoryboardSegue)
{
}
var products = [Product]()
override func viewDidLoad()
{
// Do any additional setup after loading the view.
self.ProductsCollection.delegate = self
self.ProductsCollection.dataSource = self;
super.viewDidLoad()
}
internal func LoadProducts(productsToSellOrBuy: String)
{
if productsToSellOrBuy != "ToBuy" && productsToSellOrBuy != "ToSell"
{
// Throw error
}
// Set firebase reference
ref = Database.database().reference()
let loggedOnUserID = Auth.auth().currentUser?.uid
// Retrieve the products and listen for changes
databaseHandle = ref?.child("Products").observe(.childAdded, with:
{ (snapshot) in
// Code to execute when new product is added
let prodValue = snapshot.value as? NSDictionary
let prodToLoad = prodValue?[productsToSellOrBuy] as? Bool // Checks if this is to sell or buy
if loggedOnUserID != prodValue?["Owner"] as? String, prodToLoad == true
{
let prodName = prodValue?["Name"] as? String ?? ""
let prodPrice = prodValue?["Price"] as? Double ?? -1
let prodDesc = prodValue?["Description"] as? String ?? ""
let prodURLS = prodValue?["MainImage"] as? String
let prodAmount = prodValue?["Amount"] as? Int ?? 0
let prodID = snapshot.key
let prodToAddToView = Product(name: prodName, price: prodPrice, currency: "NIS", description: prodDesc, location: "IL",
toSell: false, toBuy: false, owner: "No one", uniqueID: prodID, amount: prodAmount, mainImageURL: prodURLS)
self.products.append(prodToAddToView)
DispatchQueue.main.async {
self.ProductsCollection.reloadData()
}
}
})
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return products.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "product_collection_cell", for: indexPath) as! ProductsCollectionViewCell
//cell.ProductName.text
let requestListenRef = ref?.child("Products").child(products[indexPath.row].UniqueID()).child("MainImage")
requestListenRef?.observe(DataEventType.value, with:
{
(snapshot) in
let mainImage = snapshot.value as? String
if mainImage == nil
{
// No main image exists
cell.ProductImageView.image = UIImage(named: "DefaultProductImage")
}
else
{
// Main Image exists
let url = URL(string: mainImage!)
if let data = try? Data(contentsOf: url!)
{
cell.ProductImageView.image = UIImage(data: data)
}
}
})
// Set fields
let prodInCell = products[indexPath.row]
cell.ProductName.text = prodInCell.Name()
cell.ProductPrice.text = String(prodInCell.Price())
cell.productUniqueID = prodInCell.UniqueID()
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
{
// Display selected Item
prodToLoad = products[indexPath.row]
performSegue(withIdentifier: "view_product_information", sender:self )
}
var prodToLoad: Product?
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
if segue.identifier == "view_product_information"
{
let prodPageView = segue.destination as! ProductPageView
PrepareProductForSegue(prodPageView: prodPageView)
}
}
internal func PrepareProductForSegue(prodPageView: ProductPageView)
{
prodPageView.productToDisplay = prodToLoad
prodPageView.pictures = [UIImageView] ()
if (prodToLoad?.Images().count != 0)
{
let mainImage = prodToLoad?.GetMainImageURLString()
// Main Image exists
let url = URL(string: mainImage!)
if let data = try? Data(contentsOf: url!)
{
let imageView = UIImageView()
imageView.image = UIImage(data: data)
prodPageView.pictures?.append(imageView)
}
}
else
{
let imageView = UIImageView()
imageView.image = #imageLiteral(resourceName: "DefaultProductImage")
prodPageView.pictures?.append(imageView)
}
}
/*
// 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?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
Also :
When I delete the connection, app doesn't crash. Once I reconnect, it crashes again. The ProductsCollection is now shared between two INHERITING views.

When tapped on search bar, the app crashes

The app crashes with the following error when the search bar is tapped:
Not able to understand why?
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'unable to dequeue a cell with identifier ContactCell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'
This is my code:
import UIKit
class ContactViewController: UITableViewController, UISearchResultsUpdating {
var dataSource: [Contact] = []
var filteredResult = [Contact]()
let searchController = UISearchController(searchResultsController: nil)
override func viewDidLoad() {
super.viewDidLoad()
downloadJSONUrl()
}
func downloadJSONUrl() {
let urlString = "https://us-central1-practo-contacts-sample.cloudfunctions.net/get"
let url = NSURL(string: urlString)
URLSession.shared.dataTask(with: (url as URL?)!, completionHandler: {(data, response , error) -> Void in
if let jsonObj = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? NSDictionary {
if let NameArray = jsonObj?.value(forKey: "contacts") as? [[String: Any]] {
for names in NameArray {
var cont = Contact()
if let name = names["name"] as? String {
cont.name = name
}
if let ph = names["number"] as? String {
cont.phoneNumber = ph
}
self.dataSource.append(cont)
}
self.dataSource.sort {$0.name.lowercased() < $1.name.lowercased()}
}
OperationQueue.main.addOperation {
self.tableView.reloadData()
}
}
}).resume()
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.dataSource.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ContactCell", for: indexPath) as UITableViewCell
let contact = self.dataSource[indexPath.row]
cell.textLabel?.text = contact.name
cell.detailTextLabel?.text = contact.phoneNumber
return cell
}
func updateSearchResults(for searchController: UISearchController) {
if searchController.searchBar.text! == "" {
filteredResult = dataSource
} else {
filteredResult = dataSource.filter { $0.name.lowercased().contains(searchController.searchBar.text!.lowercased()) }
}
self.tableView.reloadData()
}
#IBAction func unwindToContactList(segue: UIStoryboardSegue) {
guard let viewController = segue.source as? AddOrEditViewController else { return }
if let name = viewController.nameTextField.text, let phoneNumber = viewController.phoneNumberTextField.text {
let contact = Contact(name: name, phoneNumber: phoneNumber)
self.dataSource.append(contact)
tableView.reloadData()
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "contactDetailsSegue" {
guard let viewController = segue.destination as? ContactDetialsViewController else {
return
}
guard let indexPath = tableView.indexPathForSelectedRow else { return }
let contact = self.dataSource[indexPath.row]
viewController.contact = contact
}
}
}
Are you sure that you have set the identifier for the cell on the storyboard and the identifier name is the same used on the code "ContactCell"?

how to deal with alamofire's async?

I am trying to save the data I got from the JSON fire however, because of Alamofire's async nature I dont get the data I need instantly but only when I tap on the tableviewcell again (and the data is wrong too)
I am wondering what I should do here so that when I tap the tableviewcell it will get the data I needed (instead of empty arrays)
Here's my code:
class CurrencyExchangeViewController: UIViewController,UITableViewDelegate, UITableViewDataSource {
let exchange = currencyExchangeModel()
var past30DaysDateValueToPass = [String]()
var past30DaysPriceValueToPass = [Double]()
var valueToPass = ""
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let indexPath = tableView.indexPathForSelectedRow!
let currentCell = tableView.cellForRow(at: indexPath) as? CurrencyExchangeTableViewCell
valueToPass = Array(self.exchange.currencyToGetExchangesDictionary.keys)[indexPath.row]
self.getPastData(currency: valueToPass)
performSegue(withIdentifier: "currencyHistorySegue", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "currencyHistorySegue") {
var viewController = segue.destination as? CurrencyHistoricalDataViewController
viewController?.historicalCurrency = valueToPass
viewController?.past30DaysPrice = self.exchange.currencyPast30DaysPriceArray
viewController?.past30DaysDate = self.exchange.currencyPast30DaysDatesArray
}
}
func getPastData(currency: String){
Alamofire.request("https://api.coindesk.com/v1/bpi/historical/close.json?currency=\(currency)").responseJSON{ (responseData) in
if responseData.result.value != nil {
let responseJSON = JSON(responseData.result.value)["bpi"].dictionaryObject
self.exchange.currencyPast30DaysDatesArray = Array(responseJSON!.keys)
self.exchange.currencyPast30DaysPriceArray = Array(responseJSON!.values) as! [Double]
}
}
}
}
If you want performSegue execute after getting data from Alamofire then remove
performSegue(withIdentifier: "currencyHistorySegue", sender: self)
this line to from didselect and place at getPastData.
func getPastData(currency: String){
Alamofire.request("https://api.coindesk.com/v1/bpi/historical/close.json?currency=\(currency)").responseJSON{ (responseData) in
if responseData.result.value != nil {
let responseJSON = JSON(responseData.result.value)["bpi"].dictionaryObject
self.exchange.currencyPast30DaysDatesArray = Array(responseJSON!.keys)
self.exchange.currencyPast30DaysPriceArray = Array(responseJSON!.values) as! [Double]
performSegue(withIdentifier: "currencyHistorySegue", sender: self)
}
}
This will help.

Swift Firebase UISearchController Index Out Of Range

I have a tableview that lists all of my "places" from firebase. I have a UISearchController to obviously search through these "places". The problem is when I just tap on the UISearchController but don't type anything and select a "place" I get a index out of range error. If I am typing or do not activated the UISearchController, it segues fine. Just when it is active and don't type is when I get the error. It throws the error on "let user = filteredUsers[indexPath.row]"
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
if segue.identifier == "BusinessProfiles" {
// gotta check if we're currently searching
if self.searchController.isActive {
if let indexPath = tableView.indexPathForSelectedRow {
let user = filteredUsers[indexPath.row]
let controller = segue.destination as? BusinessProfilesViewController
controller?.otherUser = user
}
} else {
if let indexPath = tableView.indexPathForSelectedRow {
let user = usersArray[indexPath.row]
let controller = segue.destination as? BusinessProfilesViewController
controller?.otherUser = user
}
}
}
}
As you say, you did not perform any search and select a place, right? If so, you call empty filteredUsers[indexPath.row] with indexPath.row of selected row, which have an positive index. As so, you must first check if search was perform, and only then call filteredUsers[indexPath.row] like this:
if !filteredUsers.isEmpty {
if self.searchController.isActive {
if let indexPath = tableView.indexPathForSelectedRow {
let user = filteredUsers[indexPath.row]
Just added this "&& searchController.searchBar.text != ""
" to correct my problem
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
if segue.identifier == "BusinessProfiles" {
// gotta check if we're currently searching
if self.searchController.isActive && searchController.searchBar.text != "" {
if let indexPath = tableView.indexPathForSelectedRow {
let user = filteredUsers[indexPath.row]
let controller = segue.destination as? BusinessProfilesViewController
controller?.otherUser = user
}
} else {
if let indexPath = tableView.indexPathForSelectedRow {
let user = usersArray[indexPath.row]
let controller = segue.destination as? BusinessProfilesViewController
controller?.otherUser = user
}
}
}
}

Search bar will only show first Cell in detail view

I have created a TableView with a Search Bar in Swift.
I have created 3 searchable objects. (131, 132, 137)
When i search for 137 and press 137(which is the only option to press), i still get to 131 detailView. Anyone know why? Thanks
Here is my code:
import UIKit
class TableViewController: UITableViewController, UISearchResultsUpdating {
let TitleList = ["131","132","137"]
override func viewDidLoad() {
super.viewDidLoad()
self.resultSearchController = UISearchController(searchResultsController: nil)
self.resultSearchController.searchResultsUpdater = self
self.resultSearchController.dimsBackgroundDuringPresentation = false
self.resultSearchController.searchBar.sizeToFit()
self.tableView.tableHeaderView = self.resultSearchController.searchBar
self.tableView.reloadData()
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: TableViewCell = tableView.dequeueReusableCellWithIdentifier("Cell") as! TableViewCell
cell.LabelTitle.text = TitleList[indexPath.row]
cell.CellDescription.text = DescriptionList[indexPath.row]
let imagename = UIImage(named: ImageList[indexPath.row])
cell.CellImage.image = imagename
if self.resultSearchController.active {
cell.LabelTitle?.text = self.filteredfarger[indexPath.row]
}
else {
cell.LabelTitle?.text = self.TitleList[indexPath.row]
}
return cell
}
func updateSearchResultsForSearchController(searchController: UISearchController) {
self.filteredfarger.removeAll(keepCapacity: false)
let searchPredicate = NSPredicate(format: "SELF CONTAINS[c] %#", searchController.searchBar.text!)
let array = (self.TitleList as NSArray).filteredArrayUsingPredicate(searchPredicate)
self.filteredfarger = array as! [String]
self.tableView.reloadData()
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "DetailView") {
let VC = segue.destinationViewController as! DetailedViewController
if let indexpath = self.tableView.indexPathForSelectedRow {
let Title = TitleList[indexpath.row] as String
VC.SentData1 = Title
}
Because when you do the search the row for 137 is 0, and then in prepareForSegue you use the tableView.indexPathForSelectedRow (which means indexPath.row == 0) to get the title data. Try doing something like in your tableView.cellForRowAtIndexPath function where you check if resultSearchController is active or not, and if it is use filteredfarger instead of TitleList. Something like this maybe:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "DetailView") {
let VC = segue.destinationViewController as! DetailedViewController
if let indexpath = self.tableView.indexPathForSelectedRow {
if self.resultSearchController.active {
let Title = self.filteredfarger[indexpath.row]
VC.SentData1 = Title
}
else {
let Title = TitleList[indexpath.row] as String
VC.SentData1 = Title
}
}
}
}
Better yet, you already give the cell some info, why not just use that, kinda like this:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "DetailView") {
let VC = segue.destinationViewController as! DetailedViewController
if let cell = sender as? TableViewCell {
VC.SentData1 = cell.LabelTitle.text
}
}
}