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().)
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 am about to pass data from a ViewController going to another ViewController using segue. When checking the data(event) from a variable thru breakpoint in the 1st View Controller the data(event) is not nil. But when I checked the 2nd View Controller the data(event) is nil. I am confuse whether if the reason is, I have error in my codes or because of the error appeared in my console that says Unable to insert COPY_SEND. Hope I can get some help from you. Thank you
Segue from First View Controller
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "DashBoardViewController" {
let dashBoardController = segue.destination as! DashBoardViewController
dashBoardController.self.event = event
dashBoardController.self.passcode = passcode
}
}
Event and Passcode Turns Nil
override func viewDidLoad() {
super.viewDidLoad()
guard event != nil, passcode != nil else {
_ = SCLAlertView(appearance: appearance).showError("No Event Details", subTitle: "There's no event details, please logout and try again")
return
}
showEventDetails()
}
showEventDetails
func showEventDetails() {
DispatchQueue.main.async{
self.eventNameLabel.text = "\(self.event.name.uppercased())"
self.locationLabel.text = "\(self.event.location.uppercased())"
if let dateStringFromDate = getFormattedStringFromDate(date: (self.event.startDateTime), formatString: "MMMM dd, yyyy/ hh:mm a") {
self.dateTimeLabel.text = dateStringFromDate
} else {
self.dateTimeLabel.text = "-"
}
}
}
I am assuming you linked the segue which goes to DashBoardViewController on your submitButton by Interface Builder, which means when you are tapping on the submit button, the #IBAction func submitButton(_ sender: UIButton) { } gets called, where you check if your passcode is good to go and if so you are calling validateEventPasscode() which calls an API endpoint (asynchronous) and only there you are populating the self.event = event (line 187 in ViewController.swift).
Now, what really happens is that when you link a segue from a button by IB (interface builder), there will be a perform segue internally which we have to stop by overriding the following method in ViewController.swift: source
func shouldPerformSegue(withIdentifier identifier: String,
sender: Any?) -> Bool {
return false
}
This way your call from line 190 - ViewController.swift:
self.performSegue(withIdentifier: "showEventDashboard", sender: self)
is the one that fires:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "DashBoardViewController" {
let dashBoardController = segue.destination as! DashBoardViewController
dashBoardController.event = event
dashBoardController.passcode = passcode
}
}
You can test my theory by placing three breakpoints in ViewController.swift:
line 134 at validateEventPasscode() from submitButton IBAction func;
line 190 at self.performSegue(withIdentifier: "showEventDashboard", sender: self) from validateEventPasscode() func;
line 108 at dashBoardController.event = event from prepare(for segue, sender) func;
Buggy order of execution: 1, 3, 2 - at the moment this I would expect if my theory is correct;
Desired order of execution: 1, 2, 3.
Long story short, you populate your self.event after you perfomSegue and go to the next screen, that's why your event is nil in the next VC.
I used as reference the ViewController.swift file from your repo: ViewController.swift
Hope it helps, cheers!
Replace your prepareForSegue method in FirstViewController with this
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "DashBoardViewController" {
let dashBoardController = segue.destination as! DashBoardViewController
dashBoardController.event = event
dashBoardController.passcode = passcode
}
}
you don't need to write
dashBoardController.self.event = event
dashBoardController.self.passcode = passcode
Just remove self from above two lines.
I have included images to hopefully make this easier to understand. My FirstViewController has a collection view with a list of users from my firebase database.When I click on the users I am segued to a DetailedViewController that has more information about the user that was clicked. Within that viewController, the goal is to click on the compose button and segue to a viewController that allows me to chat with the user, whose profile I clicked on.
I have gotten as far as this for segueing from DetailedViewController to Message user 1.
#IBAction func SendMessage(_ sender: Any) {
performSegue(withIdentifier: "chat", sender: self)
}
I am not sure how to make sure I am sending the particular user I click on a message.
This is how I am passing data from FirstViewController to DetailedViewController.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "details" {
if let indexPaths = self.CollectionView!.indexPathsForSelectedItems{
let vc = segue.destination as! BookDetailsViewController
let cell = sender as! UICollectionViewCell
let indexPath = self.CollectionView!.indexPath(for: cell)
let post = self.posts[(indexPath?.row)!] as! [String: AnyObject]
let Booked = post["title"] as? String
vc.Booked = Booked
print(indexPath?.row)
} }
One route to take is in your DetailViewController class, or whatever class you have implementing performSegue(withIdentifier:, sender:), add this:
override public func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard segue.identifier == "chat", let dest = segue.destination as? ChatViewController else {
return
}
dest.user = self.user
}
I don't know how you have the user declared but if user is an object, do what I have above. If you're saying user as a way to umbrella multiple properties you want to pass.. do this:
dest.name = self.name
dest.number = self.number
//etc
prepare(for segue:, sender:) allows you to intercept any segue from it's class and set up whatever variables needed before the performSegue(...) executes. To target your code to a specific segue/destination/situation, make sure your code runs a check on those constraints before executing; otherwise the code will execute on all segues implemented in the class. In my example, I used your segue's identifier as that check.
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.
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 :)