EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0) with Table View - swift

I am making an application which has nearly three buttons in its Table View Cell. One for sharing and the other two for other purpose. Before adding the sharing button code everything was working fine for me , the other two buttons were working great but now as soon as I wrote the function for sharing button I am getting an error in the 'Else' part in the IndexPath statement. Here the cell leads to another view controller. Please help me understand where I am going wrong.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showView"
{
let vc = segue.destinationViewController as UIViewController
let controller = vc.popoverPresentationController
//vc.preferredContentSize = CGSizeMake(265, 400)
if controller != nil
{
controller?.delegate = self
}
}
else{
//STotal.removeAll()
let indexpath: NSIndexPath = self.tableViews.indexPathForSelectedRow!
//error in the above line
//let DestViewController = segue.destinationViewController as! CellViewController
var newName : String
var newDetails : String
var newTime : String
var newTitle : String
var newImage : UIImage
newName = names[indexpath.row]
newTitle = breeds[indexpath.row]
newTime = timeDisp[indexpath.row]
newDetails = texty[indexpath.row] as! String
newImage = images[indexpath.row]!
SName = newName
STitle = newTitle
STime = newTime
SDetails = newDetails
SImages = newImage
// DestViewController.SName = newName
self.tableViews.deselectRowAtIndexPath(indexpath, animated: true)
}
}

I think your problem might be that when you click on the shared button, the cell does not go into the "selected" state and so the method you call
self.tableViews.indexPathForSelectedRow!
leads to a crash, because there is not selected row and you force unwrap it.
One possible solution might be something like this:
func didPressShareButton(sender: UITableViewCell) {
let indexPath = tableView.indexPathForCell(sender)
// prepare your DetailViewController now / show segue
}

Check to see if an indexPath is actually selected.
var indexPath = NSIndexPath()
if let path = tableViews.indexPathForSelectedRow {
indexPath = path
} else {
//Do something if there is no indexPath selected
return//end function
}

Related

In Xcode 11.4, I Cannot Hide A Button

So I am trying to make an Painting app that can store the data that user gives (such as name of the painting, artist of the painting, year of the painting and image of the painting) and shows in a table view. I have 2 view controllers, first one is called ViewController that has a table view for showing the data (only name of the painting for the table view cell) and the second one is called DetailedVC which is for entering and saving the data, also showing the details of the data. In the second view controller I added 3 text fields, 1 image view that enables the user to go the photo library and a save button. I wrote this in my DetailedVC script for when the user taps one of the elements from the table view in my ViewController:
override func viewDidLoad() {
super.viewDidLoad()
if chosenPainting != "" {
savebutton.isHidden = true //Trying to hide the save button here!!
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let fetch = NSFetchRequest<NSFetchRequestResult>(entityName: "Paintings")
let idString = chosenPaintingId?.uuidString
fetch.predicate = NSPredicate(format: "id = %#", idString!)
fetch.returnsObjectsAsFaults = false
do {
let results = try context.fetch(fetch)
if results.count > 0 {
for result in results as! [NSManagedObject] {
if let name = result.value(forKey: "name") as? String {
nameText.text = name
}
if let artist = result.value(forKey: "artist") as? String {
artistText.text = artist
}
if let year = result.value(forKey: "year") as? Int {
yearText.text = String(year)
}
if let imageData = result.value(forKey: "image") as? Data {
let imageD = UIImage(data: imageData)
imageView.image = imageD
}
}
}
} catch {
print("error")
}
} else {
savebutton.isEnabled = false
nameText.text = ""
artistText.text = ""
yearText.text = ""
}
I get the information from my ViewController like this:
#objc func AddButtonTapped(){
selectedPainting = ""
performSegue(withIdentifier: "toDetailedVC", sender: self)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedPaintingId = ids[indexPath.row]
selectedPainting = names[indexPath.row]
performSegue(withIdentifier: "toDetailedVC", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toDetailedVC" {
let destination = segue.destination as! DetailedVC
destination.chosenPainting = selectedPainting
destination.chosenPaintingId = selectedPaintingId
}
}
It works just fine below the savebutton.isHidden = true. I don't understand why the button is not disappearing. Everything else works correctly, text field's texts turn into the information that user gave but, the button is standing still right in the bottom. And even the savebutton.isEnabled = false is working. I thought there must be a problem with connection between my script and my storyboard so I deleted and did it again, but it didn't work again. I must have doing something wrong, can you help me about this?

Tableview keeps going back to top? After leaving to "detailed" vc?

How can I prevent the view controller from starting from the top of the feed when a user leaves and goes back?
Basically, I have the main VC and a detailed VC. When the user selects a cell, it should jump to the detailed VC. If she/he goes back, it should leave her back to where she/he was.
I get that my code is calling "reload Data" every time the VC loads, but what other options do I have then if I don't call that method?
Here's an image of my main storyboard if it helps. Main VC(left) is the feed tableView where the user can tap on the cell. When he/she taps on the cell, it "segues" to the comment table VC (right). When he/she is done commenting she/he can go back to the main VC and continuing going down the feed. (ideally, except it keeps loading from the newest post, rather than segueing the user back to where she/he was down in the feed)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "postCell", for: indexPath) as! PostCell
let post: PostModel
post = postList[indexPath.row]
func set(post: PostModel) {
ImageService.downloadImage(withURL: post.author.patthToImage) { image in
cell.profileImage.image = image
}
}
set(post: postList[indexPath.row])
cell.descriptionLabel.numberOfLines = 0 // line wrap
cell.descriptionLabel.lineBreakMode = NSLineBreakMode.byWordWrapping
cell.descriptionLabel.text = post.message
cell.authorLabel.text = post.author.username
cell.timeLabel.text = post.createdAt.calendarTimeSinceNow()
//takes care of post image hidding and showing
if self.postList[indexPath.row].pathToImage != "" {
cell.postImage.isHidden = false
cell.postImage?.downloadImage(from: self.postList[indexPath.row].pathToImage)
} else {
cell.postImage.isHidden = true
}
if cell.postImage.isHidden == true {
cell.postImage.frame = CGRect(x: 0, y: 0, width: 0, height: 0)
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let post: PostModel
post = postList[indexPath.row]
myIndex = indexPath.row
myPost = post.postID!
performSegue(withIdentifier: "segue", sender: self)
print(myIndex)
print(post.postID)
}
override func viewDidLoad() {
super.viewDidLoad()
beginBatchFetch()
}
func beginBatchFetch() {
fetchingMore = true
fetchPosts { newPosts in
self.postList.append(contentsOf: newPosts)
self.endReached = newPosts.count == 0
self.fetchingMore = false
self.tableViewPost.reloadData()
}
}
func fetchPosts(completion: #escaping(_ postList:[PostModel])->()) {
ref = Database.database().reference().child("posts")
var queryRef:DatabaseQuery
let lastPost = self.postList.last
if lastPost != nil {
let lastTimestamp = lastPost!.createdAt.timeIntervalSince1970 * 1000
queryRef = ref.queryOrdered(byChild: "timestamp").queryEnding(atValue: lastTimestamp).queryLimited(toLast:20)
} else {
queryRef = ref.queryOrdered(byChild: "timestamp").queryLimited(toLast:20)
}
queryRef.observeSingleEvent(of: .value, with: { snapshot in
var tempPosts = [PostModel]()
for child in snapshot.children {
if let childSnapshot = child as? DataSnapshot,
let dict = childSnapshot.value as? [String:Any],
let author = dict["author"] as? [String:Any],
let uid = author["uid"] as? String,
let username = author["username"] as? String,
let fullname = author["fullname"] as? String,
let patthToImage = author["patthToImage"] as? String,
let url = URL(string:patthToImage),
let pathToImage = dict["pathToImage"] as? String,
let likes = dict["likes"] as? Int,
let postID = dict["postID"] as? String,
let message = dict["message"] as? String,
let genre = dict["genre"] as? String,
let timestamp = dict["timestamp"] as? Double {
let userProfile = UserProfile(uid: uid, fullname: fullname, username: username, patthToImage: url)
let post = PostModel(genre: genre, likes: likes, message: message, pathToImage: pathToImage, postID: postID, userID: pathToImage, timestamp: timestamp, id: childSnapshot.key, author: userProfile)
tempPosts.insert(post, at: 0)
}
}
//first two
self.postList = tempPosts
self.tableViewPost.reloadData()
// return completion(tempPosts)
})
The issue, as Matt pointed out, is that you are segueing to the detailVC and then segueing back to the original VC. This is creating a new instance of the original VC.
What you should do, in your VC with the table view, is instantiate and present the destination view controller when a cell is selected. So you should replace performSegue(withIdentifier: "segue", sender: self) with something like:
let storyboard = UIStoryboard(name: "Main", bundle: .main)
var destinationVC = (storyboard.instantiateViewController(withIdentifier: "DestinationVC") as! DestinationViewController)
present(destinationVC!, animated: ture, completion: nil)
NOTE: View Controller storyboard identifiers can be set in the interface builder. So, if you are wanting to use this line: var destinationVC = (storyboard.instantiateViewController(withIdentifier: "DestinationVC") as! DestinationViewController), you will first have to set the Storyboard ID in the interface builder:
Now, within your destination view controller, instead of segueing when your done button is pressed, you want to use the dismiss method to dismiss the presented view controller.
class DestinationViewController: UIViewController {
#IBAction func backButtonPressed(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
}
Now, the original VC with the table view stays in memory while the destination VC is presented over it. When the user presses the "back" button, it will dismiss the destination VC and the original VC should reappear as you left it.
"I added a "back" button to segue back" That's the problem. The way to go back is to call dismiss — not use a second segue (unless it is a special "unwind" segue, but you don't know how to do that).

setup navigation controller View Controller as a CNContactViewController black screen

I have a collection view with some cells representing a contact (their data has a phone number and name) and I am trying to add the contact to the iPhone contacts. I have created a segue from a button called "add contact" that is inside the CollectionViewCell to a navigation controller, and set its identifier as "ADD_CONTACT".
In the storyboard, my segue has a navigation controller with no root view controller.
in prepareToSegue of the view controller that delegates my UICollectionView I wrote this code:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == ADD_CONTACT {
let dest = segue.destination as! UINavigationController
if let cell = sender as? SBInstructionCell {
if cell.isContact {
let newContact = CNMutableContact()
if let phone = cell.instructionBean?.contactAttachment?.phoneNumber{
newContact.phoneNumbers.append(CNLabeledValue(label: "home", value: CNPhoneNumber(stringValue: phone)))
}
if let name = cell.instructionBean?.contactAttachment?.contactName {
newContact.givenName.append(name)
}
let contactVC = CNContactViewController(forNewContact: newContact)
contactVC.contactStore = CNContactStore()
contactVC.delegate = self
dest.setViewControllers([contactVC], animated: false)
}
}
}
}
this results with a black screen.
How can this be fixed? I want to see the CNContactViewController
Eventually I solved this in a different approach using Closures.
In my UICollectionViewCell
I added this var:
var closureForContact: (()->())? = nil
Now on my button's action in the same cell I have this func:
#IBAction func addContactTapped(_ sender: UIButton) {
if closureForContact != nil{
closureForContact!()
}
}
Which calls the function.
In my CollectionView in cell for item at index path, I set the closure like this:
cell.closureForContact = {
if cell.isContact {
let newContact = CNMutableContact()
if let phone = cell.instructionBean?.contactAttachment?.phoneNumber{
newContact.phoneNumbers.append(CNLabeledValue(label: "home", value: CNPhoneNumber(stringValue: phone)))
}
if let name = cell.instructionBean?.contactAttachment?.contactName {
newContact.givenName.append(name)
}
let contactVC = CNContactViewController(forNewContact: newContact)
contactVC.contactStore = CNContactStore()
contactVC.delegate = self
contactVC.allowsEditing = true
contactVC.allowsActions = true
if let nav = self.navigationController {
nav.navigationBar.isTranslucent = false
nav.pushViewController(contactVC, animated: true)
}
}
}
This worked perfectly. I learned that for navigating from a cell, it is best to use closures.

Finding nil in prepareForSegue when trying to set UITextField text from Core data object

I have a UITableView that uses an NSFetchedResultsController to fetch my data for the UITableView. When a user selects a row in the UITableView, I have an edit modal view slide up with two UITextFields for the user to edit the data that was in the UITableViewCell. What I am trying to accomplish is setting the text of each UITextField with the text that was in the UITableViewCell from Core data. I am doing this so that the user does not have to re-type their data just to make an edit.
The issue is when I try to set the text for each UITextField in prepareForSegue, I am getting a fatal error " unexpectedly found nil while unwrapping an Optional value". I know that the data is there, so I am unsure exactly why it is finding nil. I followed this stackoverflow post to write my prepareForSegue function correctly when using an NSFetchedResultsController. This is my code:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "edit" {
if let inputController = segue.destinationViewController as? QAInputViewController {
let uri = currentDeck!.objectID.URIRepresentation()
let objectID = managedContext.persistentStoreCoordinator?.managedObjectIDForURIRepresentation(uri)
inputController.currentDeck = managedContext.objectWithID(objectID!) as? Deck
if let indexPath = tableView.indexPathForCell(sender as! DisplayTableViewCell) {
let data = fetchedResultsController.objectAtIndexPath(indexPath) as? Card
inputController.questionInput.text = data?.question
inputController.answerInput.text = data?.answer
}
}
}
}
Thank you for your help.
I found that my the crash was happening because my destination view controller's view was not loaded yet when trying to assign text to each UITextField.
So I added two variables of type String in my destination view controller to hold the data from my prepareForSegue function instead of trying to assign the text from the selected UITableView row directly to the UITextField and then in viewWillAppear I assigned those variables to each UITextField.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "edit" {
if let inputController = segue.destinationViewController as? QAInputViewController {
let uri = currentDeck!.objectID.URIRepresentation()
let objectID = managedContext.persistentStoreCoordinator?.managedObjectIDForURIRepresentation(uri)
inputController.currentDeck = managedContext.objectWithID(objectID!) as? Deck
if let indexPath = tableView.indexPathForCell(sender as! DisplayTableViewCell) {
let data = fetchedResultsController.objectAtIndexPath(indexPath) as? Card
inputController.selectedQuestion = data!.question
inputController.selectedAnswer = data!.answer
}
}
}
}
In my destination view controller:
var selectedQuestion: String = ""
var selectedAnswer: String = ""
override func viewWillAppear(animated: Bool) {
questionInput.text = selectedQuestion
answerInput.text = selectedAnswer
}

prepareForSegue works on iOS7 but not on iOS8

I've been struggling with this one for a while. The following code runs fine when build for ios7, but when build for ios8 just crashes. Any of these got deprecated on ios8?
Not sure if I am doing wrong by pushing a segue from a tab bar controller to a navigation controller.
If I comment my var moveVC I can transit to the HobbieFeedViewController, but then I do not pass any values to the next segue.
If I remove the navigation controller from HobbieFeedViewController the segue stops working.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
println("SENDER->\(sender)")
let cell = sender as! HobbiesTableViewCell
var cellTitle = String()
//unwrap safelly
// if let object = cell.textLabel?.text
if let object = cell.cellTitle.text
{
cellTitle = object
println("CELLTITLE->\(cellTitle)")
}
// if not
else
{
println("Could not get cellTitle")
}
// call segue
if (segue.identifier == "hobbieFeed")
{
var selectedRowIndex = self.myTableView.indexPathForSelectedRow()
// var moveVC: HobbieFeedViewController = segue.destinationViewController as! HobbieFeedViewController
//
// moveVC.moveId = selectedRowIndex!.row
// moveVC.selectedHobbie = cellTitle
println("PREPARE ROW->\(selectedRowIndex)")
println("PREPARE HOBBIE->\(cellTitle)")
}
}