So I'm just learning the basics of Swift. I have a table view of items and when an item is clicked I want to show a view that displays the details of that specific item. I have the below code in which I'm trying to achieve this, although, I'm getting a Bad Instruction runtime error. Any idea where I'm going wrong? Thanks!
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.
super.prepare(for: segue,sender: sender)
switch(segue.identifier ?? "") {
//the add item button is pressed
case "AddItem":
os_log("Adding a new donation.", log: OSLog.default, type: .debug)
//an existing item is pressed
case "ShowDetailDrill":
guard let itemViewController = segue.destination as? ItemViewController else {
fatalError("Unexpected destination: \(segue.destination)")
}
guard let selectedDonationItemCell = sender as? DonationItemCell else {
fatalError("Unexpected sender: \(sender)")
}
guard let indexPath = tableView.indexPath(for: selectedDonationItemCell) else {
fatalError("The selected cell is not being displayed by the table")
}
let selectedDonation = donatedItems.getItem(index: indexPath.row)
//TODO: load this to SubmissionVC
itemViewController.donatedItem = selectedDonation
default:
fatalError("Unexpected Segue Identifier; \(segue.identifier)")
}
}
Update
I forgot to mention that it breaks here
Update 2: Ok so I removed the navigation controller and changed the segue from a "present modally" to "show". It goes directly from the cell to the item controller. Everything now works, although I'm still somewhat confused on why it wasn't working with the navigation controller. If someone is able to explain the why, I'll mark that as the answer.
is the ShowDetailDrill segue's origin from the tableviewcell? if yes, try deleting it and then creating a new segue from the tableviewcontroller to ItemViewController instead.
Related
I have a button on one view controller and want it to take me to another view controller while carrying over some user input.
I am able to build and run with no problem, but nothing happens when I click on the button. The unwind segue just doesn't work. I'd really appreciate some help... This is my code for the initial view controller:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//configure this destination view controller only when save button is pressed
guard let button = sender as? UIBarButtonItem, button === saveButton else {
os_log("save button was not pressed, cancelling", log: OSLog.default, type: .debug)
return
}
let mainGoal = mainGoalTextField.text ?? ""
let microGoal = microGoalTextField.text ?? ""
//set the habit to be passed on to tableViewController after the unwind segue
habit = Habit(mainGoal: mainGoal, microGoal: microGoal)
}
This is my code for the second view controller:
#IBAction func unwindToHabitList(sender: UIStoryboardSegue) {
if let sourceViewController = sender.source as?ViewController, let habit = sourceViewController.habit {
let newIndexPath = IndexPath(row: habits.count, section: 0)
habits.append(habit)
tableView.insertRows(at: [newIndexPath], with: .automatic)
}
}
I connected the saveButton to the the unwind segue in the storyboard already and it still does not work. Do you have any ideas where to go forward from here?
I have an App in Swift3 that consists of 2 different UITableView that show details of a Class Asset. The content of the data in both table is same/similar (there are some filters), the presentation is different. So one of this UIViewTables shows for example comments for each asset and the other is more focused on the status.
If a user clicks on a table cell, both UITableViews open the same UIView for full details since both tables represent the same data at the end.
I embedded a UINavigationControl and made a segue for each TableCell. If I click on the cells, the correct detail screen opens.
But if I save it, it always brings me back to the first table, even if I start from the second.
Can anybody give me a hint?
Here is my DetailViewController prepare method that is called when I click on the save button.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
print(segue.identifier)
// Configure the destination view controller only when the save button is pressed.
guard let button = sender as? UIBarButtonItem, button === btnSave else
{
//os_log("The save button was not pressed, cancelling", log: OSLog.default, type: .debug)
return
}
asset?.name = tfDeviceName.text ?? ""
//here comes some other stored asset information...
}
Here is my first UITableViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
switch(segue.identifier ?? "")
{
case "AddItem":
os_log("Adding a new asset.", log: OSLog.default, type: .debug)
case "ShowDetails":
guard let assetDetailViewController = segue.destination as? DetailViewController else {
fatalError("Unexpected destination: \(segue.destination)")
}
guard let selectedAssetCell = sender as? AssetTableViewCell else {
fatalError("Unexpected sender: \(String(describing: sender))")
}
guard let indexPath = tableView.indexPath(for: selectedAssetCell) else {
fatalError("The selected cell is not being displayed by the table")
}
selectedIndex = indexPath.row
let selectedAsset = assets[indexPath.row]
assetDetailViewController.asset = selectedAsset
default:
fatalError("Unexpected Segue Identifier; \(String(describing: segue.identifier))")
}
editMode = true
}
This is my second UITableViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
switch(segue.identifier ?? "") {
case "AddItem":
os_log("Adding a new asset.", log: OSLog.default, type: .debug)
case "ShowTask":
guard let taskDetailViewController = segue.destination as? DetailViewController else {
fatalError("Unexpected destination: \(segue.destination)")
}
guard let selectedAssetCell = sender as? TaskTableViewCell else {
fatalError("Unexpected sender: \(String(describing: sender))")
}
guard let indexPath = tableView.indexPath(for: selectedAssetCell) else {
fatalError("The selected cell is not being displayed by the table")
}
let selectedAsset = assets[indexPath.row]
taskDetailViewController.asset = selectedAsset
default:
fatalError("Unexpected Segue Identifier; \(String(describing: segue.identifier))")
}
}
And here my Story Board
Thank you!
BR
Stefan
EDIT WITH SOLUTION:
Thx to Duncans hints I found my problem. The unwind action is attached to the save button in the interface builder by crtl-click-drag the button to the exit event and select then the unwind method. And here comes the important thing: You can select only one method, but not the class. The class is selected automatically by the system that manages the navigation, but both methods (obviously) have to have the same unwind name. I made a typo so that the unwind method in the second UITableView was slightly different and then the systems doesn't find the method in the correct UITableView and jumps to a UITableView that has the correct method even is this Class was not the original segue
How are you returning from your DetailViewController back to the calling table view controller?
It sounds to me like you are using a normal segue, which is wrong.
You can either use an unwind segue, or the reverse of whatever method you use to get there (if you use a push segue, call pop(), if you use a modal present, call dismiss().)
I have the following schema:
The controller in the upper right corner is SelectAlbumVC
The controller in the lower left corner is AddPhotoVC
In SelectAlbumVC i have this code:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let destination = segue.destination as? AddPhotoPostVC
else { fatalError("unexpected view controller for segue") }
guard let cell = sender as? AlbumListCells else { fatalError("unexpected cell for segue") }
switch SegueIdentifier(rawValue: segue.identifier!)! {
case .showAllPhotos:
destination.fetchResult = allPhotos
destination.headerTitleBtnString = cell.allPhotoTitle.text!
case .showCollection:
// get the asset collection for the selected row
let indexPath = tableView.indexPath(for: cell)!
let collection: PHCollection
switch Section(rawValue: indexPath.section)! {
case .smartAlbums:
collection = smartAlbums.object(at: indexPath.row)
case .userCollections:
collection = userCollections.object(at: indexPath.row)
default: return // not reached; all photos section already handled by other segue
}
// configure the view controller with the asset collection
guard let assetCollection = collection as? PHAssetCollection
else { fatalError("expected asset collection") }
destination.fetchResult = PHAsset.fetchAssets(in: assetCollection, options: nil)
destination.assetCollection = assetCollection
destination.headerTitleBtnString = cell.collectionTitle.text!
destination.isComingFromSelectAlbum = true
}
}
so basically when i click on a cell the segue will be executed and the data passed to AddPhotoVC.
My issue is that when the segue is executed the navigation controller associated with SelectAlbumVC is not dismissed and so when clicking the dismissing button on the AddPhotoVC SelectAlbumVC is presented again (it's the last controller in the stack).
Is there a way to dismiss the navigation controller when the prepare for segue is called?
I've tried to add the the bottom
self.navigationController?.popToRootViewController(animated: false)
but it does not work.
Any help will be really appreciated.
Thank you!
If I understand your code correctly, you're adding another segue back to AddPhotoVC. So if while your app was running and you clicked the "Debug View Hierarchy" (down by the debugger controls in Xcode) you'd see that you now have another AddPhotoVC on top of the original one.
Instead of performing a segue from SelectPhotoVC to AddPhotoVC you may want to consider performing an Unwind Segue instead. This way you could pass the values you want and all the previous VCs would be dismissed.
You are using (multiple) two UINavigationController. That means you are presenting the second screen instead of pushing it.
Now, you mentioned that popToRootViewController does not work, that's because again the two screens have two different UINavigationController. You should dismiss the second screen rather than popping it, because you presented it.
Know the different between pushing/show and presenting viewControllers.
Push/Show --- Pop.
Present --- Dismiss.
Instead of this self.navigationController?.popToRootViewController(animated: false)
Use this:
self.navigationController?.dismiss(animated: true, completion: {
// completion, do something or make it nil.
})
I have a ViewController that is a CollectionViewDelegate. I wanted to segue to a detail view of the image in my CollectionView when the user taps on the image. I connected the segue from my cell to the detail VC and used this code.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//segue to detail view of image
if segue.identifier == "ShowDetail",
let detailImageView = segue.destination as? CollectionDetailViewController
{
detailImageView.imageName = sender as! UIImage
}
}
The error that showed was "Cannot convert the sender CollectionView to UIImage". Instead when I segued through my VC I was able to cast sender to UIImage. Why is that?
I don't know what or why it is working in code you haven't shown... but in this case, if sender is a cell, you cannot simply declare "now you are a UIImage" and thus it is so.
I assume you have a UIImageView inside your Collection View Cell, so more likely, you want to do something like:
if let cell = sender as? MyCustomCell {
if let image = cell.theImageView.image as? UIImage {
detailImageView.imageName = image
}
}
note: I'm just typing this, not running it, so it may need a tweak or two.
I am developing a UITableView app in Swift using storyboards. My app has two view controllers: a MasterViewController and a DetailViewController.
I am trying to replicate the behavior of Apple's Notes app. In Apple's Notes app, when a user clicks on the Trash Bar Button item displayed within the Detail View, the application shows an Action Sheet with two options: "Delete Note" and "Cancel". If the user clicks on "Delete Note", the current contents of the Detail View fade out, the current row is deleted, and the contents of the next row fade in.
I added a toolbar with a Trash Bar Button item to the detail view. I am able to call a method of the DetailViewController when the user clicks on the Trash Bar Button and am able to display the Action Sheet. I have been unable to make the current contents of the view fade out and be replaced with the contents of the next row.
How do I do that?
Here is the code that populates the detail view:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetail" {
if let indexPath = self.tableView.indexPathForSelectedRow() {
let object = objects[indexPath.row] as Note
println("setting detailItem! row: ", indexPath.row)
(segue.destinationViewController as DetailViewController).detailItem = object
(segue.destinationViewController as DetailViewController).delegate = self
}
}
}
You can go pretty straightforward way, creating a special setter in DetailViewController:
func setDetailItem(newItem: AnyObject, animated:Bool)
{
if animated
{
UIView.animateWithDuration(0.3, animations: {
<yourtextview>.alpha = 0.0
},
completion: {(finished:Bool) -> Void in
self.detailItem = newItem
<yourtextview>.alpha = 1.0 // back to visible state instantly
})
}
else
{
self.detailItem = newItem
}
}
then adding boolean flag in MasterViewController (for example, named isDeletion)and set it to true in remove button tap handler. And alter your prepareForSegue a bit:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetail" {
if let indexPath = self.tableView.indexPathForSelectedRow() {
let object = objects[indexPath.row] as Note
println("setting detailItem! row: ", indexPath.row)
// previous code
//(segue.destinationViewController as DetailViewController).detailItem = object
// new code
if isDeletion
{
isDeletion = false // don't forget to clear the flag
controller.setDetailItem(object, animated: true)
}
else
{
controller.setDetailItem(object, animated: false)
}
(segue.destinationViewController as DetailViewController).delegate = self
}
}
}
Now the text should fade out after delete and instantly change after plain selection.
Smells a bit Objective-C-ish, of course :)