I'm relatively new to Swift and am stumbling on this problem that is likely very simple to fix, I just can't figure it out and keep hitting a brick wall.
I'm making an app where a user taps an image in a collection view controller and it performs an unwind segue back to the main view controller and updates a uiimageview with the image that the user selected. I got the unwind segue to work but the problem is that the uiimageview is always one image behind (i.e. I select image A, unwind segue back and no image displays, so I select image B unwind segue back and then the original image I selected the first time, image A, is in the uiimageview).
Here is the code for the destination view controller (main view controller where I want the uiimageview to update based on the image selected in the collection view)...
#IBAction func unwindToVC(segue:UIStoryboardSegue) {
if(segue.sourceViewController .isKindOfClass(FrancoCollectionCollectionViewController))
{
let francoCollectionView:FrancoCollectionCollectionViewController = segue.sourceViewController as! FrancoCollectionCollectionViewController
let selectedImageFromLibrary = selectedFranco
dragImage!.image = UIImage(named: selectedImageFromLibrary)
}
On the collection view controller where the user selects the image and it unwinds back, I just ctrl + dragged the cell to "exit" on the storyboard and selected the unwindToVC segue.
As far as my collection view here is how I have that set up, since I have a suspiscion it's related to the didSelectItemAtIndexPath part of it but I'm not sure...
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return francoPhotos.count
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! FrancoCellCollectionViewCell
// sets each cell of collection view to be a uiimage view of francoPhotos
let image = UIImage(named: francoPhotos[indexPath.row])
cell.francoImageCell.image = image
return cell
}
// highlights cell when touched
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
selectedFranco = francoPhotos[indexPath.row]
let cell = collectionView.cellForItemAtIndexPath(indexPath)
cell!.layer.borderWidth = 3.0
cell!.layer.borderColor = UIColor.blueColor().CGColor
}
override func collectionView(collectionView: UICollectionView, didDeselectItemAtIndexPath indexPath: NSIndexPath) {
let cell = collectionView.cellForItemAtIndexPath(indexPath)
cell!.layer.borderWidth = 0.0
cell!.layer.borderColor = UIColor.clearColor().CGColor
}
Sorry if I posted way too much code but this is my first post and like I said I'm new to development, so I figured it would be better to include a little extra rather than not have something that is needed.
Any advice is greatly appreciated!
Thanks so much!!! :)
This is happening because collectionView:didSelectItemAtIndexPath: is called after the segue is triggered. So, it is always one behind.
One way to fix this is to call the unwind segue programmatically with performSegueWithIdentifier instead of wiring it from the cell to the exit. To do this, wire your exit segue from the viewController icon (left most icon) at the top of your viewController to the exit icon (right most icon).
Find this segue in the Document Outline view and give it an identifier such as "myExitSegue" in the Attributes Inspector on the right. Then in didSelectItemAtIndexPath, the last thing to do is to call performSegueWithIdentifier("myExitSegue", sender: self).
Here's what I would do:
Remove the code in the source VC unwind action for an empty unwind segue like so (the logic for displaying the new image will be put in the collection VC):
#IBAction func unwindToVC(segue: UIStoryboardSegue) {
}
Give your unwind segue a string identifier so that you may perform the segue programmatically not automatically. Here, I just called mine "Unwind".
Then, inside of FrancoCollectionCollectionViewController put a property to hold the selected UIImage:
var selectedFranco: UIImage!
Within didSelectItemAtIndexPath set the property to the selected image and then manually perform the segue:
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
// ...
self.selectedFranco = francoPhotos[indexPath.row]
self.performSegueWithIdentifier("Unwind", sender: self)
}
Then I would add the prepareForSegue method in your FrancoCollectionCollectionViewController to set the source view controller's image to the selected image:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "Unwind" {
let sourceVC = segue.destinationViewController as! YourSourceViewController
sourceVC.dragImage.image = selectedImageView.image
}
}
Related
EDIT 2: Added a half-solution at bottom. Still open to a full solution
EDIT 1: Added images.
I need help fixing navigation between a second and third tableview.
I have a Navigation Controller, and three table views. The first two show up fine such that clicking a dynamic tableview cell > in the first tableview moves to the second tableview. However the second tableview cell chevron > while set in the storyboard (Accessory: Disclosure Indicator) doesn't show up during runtime, and therefore clicking on it does not move to the third tableview. I need help fixing that.
In each case I Ctrl+linked the cells to the next tableview in storyboard as show, and have prepare for segue in the previous tableview controllers. However only one works and the other seems orphaned.
If you could suggest a checklist in making sure the Navigation Controller works across three tableviews that would be helpful. Not sure what code to show so I'll post both segues. But it might not be a segue issue so I don't know.
first tableview Weeks works going to second tableview Leagues. Loads json file depending on what is clicked.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toLeagues"
{
let leaguesController = segue.destination as! LeaguesViewController
// pass the selected row
let selectedRow = self.tableView.indexPath(for: sender as! UITableViewCell)!.row
if selectedRow == 0
{
leaguesController.weekFileName = "sports_week_1"
}
else
{
leaguesController.weekFileName = "sports_week_2"
}
}
But second tableview Leagues doesn't work going to third tableview Games
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toGames"
{
let gamesController = segue.destination as! GamesViewController
// pass the selected row
let selectedRow = self.tableView.indexPath(for: sender as! UITableViewCell)!.row
gamesController.gameName = selectedRow.description
}
}
EDIT 1: Added images:
Starts off fine in first tableview...
Stops here in second tableview, can't move to third tableview
EDIT 2: Half solution
I found a half solution. Menu item now opens new view, only that the chevron appears after the fact. didSelectRowAt adds the missing disclosure indicator directly and goes to the new view controller. Couldn't find a viewWillAppear with IndexPath so I opted for didSelectRowAt. Works when clicked at least. Just the disclosure indicator missing on initial load. How to load the accessoryType before the view runs?
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)
cell?.accessoryType = .disclosureIndicator
// force menu to move to GamesViewController
let myGamesView = self.storyboard!.instantiateViewController(withIdentifier: "gamesView")
self.navigationController?.pushViewController(myGamesView, animated: true)
}
Try setting accessoryType in cellForRowAt instead of didSelectRowAt.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "yourIdentifier") as! YourCustomCell
//Your other code
cell.accessoryType = .disclosureIndicator
return cell
}
I have made a collection view with 4 cells and when you press the button in cell 1 (I have not made the other cells yet) it will take you to a new ViewController called "FirstCollectionViewController". I have a pangesturderecognizer that shows the slide out menu when you swipe in the collection view.
But when you are in the "FirstCollectionViewController" and you want to come back to the collection view, you can press a button up in the left side corner. But when I press it, I will get an EXC_BAD_INSTRUCTION error at the "self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())" inside the ViewDidLoad in the CollectionViewController. I have tried to put the panGestureRecognizer in the ViewDidAppear but the same happens
How can i fix it?
BTW the segue which should send you back to the collection view is called: "SegueFirstCollectionViewController"
CollectionViewController :
import Foundation
import UIKit
class CollectionViewController: UICollectionViewController {
var Array = [String]()
var ButtonArray = [String]()
override func viewDidLoad() {
super.viewDidLoad()
Array = ["1","2","3","4"]
ButtonArray = ["1", "2", "3", "4"]
self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
}
override func viewDidAppear(animated: Bool) {
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return Array.count
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as UICollectionViewCell
let Label = cell.viewWithTag(1) as! UILabel
Label.text = Array[indexPath.row]
let Button = cell.viewWithTag(2) as! UIButton
Button.setTitle(ButtonArray[indexPath.row], forState: UIControlState.Normal)
// Button.addTarget(self,action:#selector(ButtonArray(_:)), forControlEvents:.TouchUpInside)
Button.addTarget(self, action: Selector("ButtonArray:"), forControlEvents:.TouchUpInside)
return cell
}
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
print("Inside didSelectItemAtIndexPath")
}
func ButtonArray(sender : UIButton) {
print("m")
if sender.currentTitle == "1"{
performSegueWithIdentifier("segueFirst", sender: nil)
}
if sender.currentTitle == "2"{
}
if sender.currentTitle == "3"{
}
if sender.currentTitle == "4"{
}
}
}
FirstCollectionViewController :
class FirstCollectionViewController : UIViewController {
#IBAction func SegueFirstCollectionViewController(sender: UIButton) {
performSegueWithIdentifier("SegueFirstCollectionViewController", sender: nil)
}
}
You are missusing segues. Segues creates new instance of their destination ViewController, meaning that when you start your app you have 1 CollectionViewController, then you click a cell and you have a stack of 1 CollectionViewController AND 1 FirstViewController, then you hit the button, and event it looks like it, you are not coming back to the original CollectionViewController, but you are creating a new one, meaning you now have 1 collectionViewController, one first viewController and one CollectionViewController.
This is why you are reexcecuting viewDidLoad, this is why it fail because the pan gesture recognizer has already been added to another view....
If you want to implement flat (push pop / master -> detail / back button...) you should encapsulate your collectionViewController in a NavigationViewController and use the freely given back button : DON'T HANDLE THE BACK YOURSELF, ESPECIALLY WITH SEGUE
PS for the sake of beeing exact but for an advertized public : a special kind of segue exist to deal with back, they are called unwind segue. However, UINavigationController should always be the preferred option for flat navigation. Unwind segue find their use when dealing with vertical navigation (modal presentation / OK - Cancel)
You don't need get the cell outside
Make set value in
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell{
//set value here
if self.currentTitle == "1" ){
//
}
else {
//...
}
}
and reload the specific cell
let indexPath = IndexPath(item: i, section: 0)
self.tableView.reloadRows(at: [indexPath], with: .automatic)
Hope it would make help for you:)
This is a weird question and counter intuitive to how tableviews work, however I have an array that creates 3 cells in the tableview. Since we are currently working on a beta and we only need the user to segue when they click on the first cell the 2 other cells DONT need to be used.
Here is some code of our tableview
import UIKit
class TableViewController: UITableViewController {
let locationManager = CLLocationManager()
//Store Array of Images
var imageArray = ["riverparkplace","mayfair","jamesonhouse",]
//Array of Image Names
var textArray = ["River Park Place", "MayFair", "Jameson House"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
locationManager.requestAlwaysAuthorization()
if KCSUser.activeUser() == nil {
print("User Not Logged In")
performSegueWithIdentifier("jobsiteTOLogin", sender: nil)
} else {
//user is logged in and will be loaded on first call to Kinvey
var currentusername = KCSUser.activeUser().givenName
print("User named:\(currentusername) has logged in ")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as UITableViewCell!
//cell.textLabel?.text = textArray[indexPath.row]
let imageView = cell.viewWithTag(1) as! UIImageView
imageView.image = UIImage(named: imageArray[indexPath.row])
let textLabel2 = cell.viewWithTag(2) as! UILabel
textLabel2.text = textArray[indexPath.row]
return cell
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return imageArray.count
}
//On Click
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
}
//Buttons
#IBAction func logoutButton(sender: AnyObject) {
if KCSUser.activeUser() == nil{
//User is not logged in
print("Cant log out user since they are not logged in!!")
}else{
KCSUser.activeUser().logout()
print("User Logged out")
performSegueWithIdentifier("jobsiteTOLogin", sender: nil)
}
}
}
As you can see I only want the view to segue when you click on "riverrockplace" if you click on anything else it is ok if the app does nothing but would be even better if it returned a pop up notification.
Also at this stage when I segue I don't need to take data with me.
There are two ways you can approach this issue, using different types of segues:
You can add a segue that fires on cell selection, by control dragging from your cell to the destination. You can then implement the method shouldPerformSegueWithIdentifier: and, using the sender figure out which cell is calling it (as that will be sender), and fire or not based on that information.
You can add a custom segue by dragging from the yellow controller icon on the top of your TableViewController to the destination. Then, in didSelectRowAtIndexPath, determine if you should segue. If you should, then call performSegueWithIdentifier:.
Implement the didSelect Delegate method like this:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if indexPath.row == 0{
// perform segue
}else{
// no action needed
}
}
You can assign userInteractionEnabled = false to the cells that you won't be using to prevent the click animation from happening, as it might be confusing to your users if they click on the cell, its styling changes and nothing happens.
Alternatively, if you want to pop up an alert to provide some useful information, you can use the didSelectRowAtIndexPath method from your code to filter the index of the selction using indexpath.row, as suggested by Md.Muzahidul
If i understood your problem correctly, what you have to do is you want to navigate to XViewController, when you click on first cell of the tableView only.
In that case you have to use conditional segues, which you can create by dragging segue by clicking on the FilesOwner and not the cell itself as shown in the image below,
Then select the segue you have just created and set its identifier in the attribute inspector as shown in below image,
Next, you need to fire the segue in the didSelectRowAtIndexPath, change your code as shown below,
//On Click
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.performSegueWithIdentifier("ConditionalNavigationToXViewController", sender: self)
}
Then check the segue as below,
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
if (segue.identifier == "ConditionalNavigationToXViewController") {
segue.destinationViewController as? XViewController
}
}
Hope this would help you.
I know, I know this has been asked a lot of times. I also found this question but the solution it suggested did not work for me.
I am just trying to build an app to demonstrate how to use those things in UIKit (in case I want to use them later on. I can just copy the code).
I have created a View Controller with a table view in it. I wrote a class called PrototypeTableController to act as the view controller class for the view controller I created in the storyboard.
When the user taps on one of the cells, I want another view controller to show, called Prototype Table Content. And different text will be shown if you tap on different cells.
In the storyboard, it's like this:
The text of the label in Prototype Table Content will be different when the user taps on a different cell. This means I need to send data from one view controller to another.
The post mentioned above suggested that I should give the segue an identifier, so I did:
Here is my code:
View controller class for the table view:
class PrototypeTableController: UIViewController, UITableViewDataSource, UITableViewDelegate {
let data = ["Cell1", "Cell2", "Cell3", "Cell4", "Cell5"]
let contents = ["Hello", "Nice", "OMG", "Jesus", "Peace"]
var content: String?
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = data[indexPath.row]
return cell
}
func tableView(tableView: UITableView, titleForFooterInSection section: Int) -> String? {
return "This is a prototype table view created by Sweeper"
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "my table"
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
content = contents[indexPath.row]
tableView.deselectRowAtIndexPath(indexPath, animated: true)
performSegueWithIdentifier("showContent", sender: tableView)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showContent" {
let destination = segue.destinationViewController as! PrototypeTableContentViewController
destination.contentString = content
}
}
}
View controller class for Prototype Table Content view:
class PrototypeTableContentViewController: UIViewController {
#IBOutlet var tableContent: UILabel!
var contentString: String?
override func viewDidLoad() {
super.viewDidLoad()
tableContent.text = contentString
}
}
I think I did all the things suggested in the post mentioned above. I added an identifier, I called performSegueWithIdentifier
, I also deselected the cell after the tapping.
However, it just doesn't go to the other view controller! It stays on the same controller! Like this:
When the user taps on one of the cells, I want another view controller to show, called Prototype Table Content. And different text will be shown if you tap on different cells.
While you can programmatically call performSegueWithIdentifier, it's a lot of effort that the storyboard can automatically handle for you. Just use a show storyboard segue from your prototype cell to PrototypeTableContentViewController.
prepareForSegue knows which cell you selected because the cell is the sender. All you have to do is set the destination view controller's contentString.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
guard let controller = (segue.destinationViewController as? PrototypeTableContentViewController where segue.identifier == "showContent", let cell = sender as? UITableViewCell, textLabel = cell.textLabel else {
return
}
controller.contentString = textLabel.text
}
This is very similar to how a template like Master-Detail segues from a cell to show details about a cell (although Apple uses indexPathForSelectedRow to pass the cell's details):
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetail" {
if let indexPath = self.tableView.indexPathForSelectedRow {
let object = objects[indexPath.row] as! NSDate
let controller = (segue.destinationViewController as! UINavigationController).topViewController as! DetailViewController
controller.detailItem = object
controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem()
controller.navigationItem.leftItemsSupplementBackButton = true
}
}
}
In either case, the SDK performs the storyboard segue for you; a segue didn't need to be programmatically added or performed.
Make sure your tableview delegate is set. If you are using storyboard, make sure delegate outlet in your storyboard is connected properly. If you are creating tableview by code, then you should do tableView.delegate=self; to set the delegate.
Your code is fine.
And one more thing:
You might need to change this line:
performSegueWithIdentifier("showContent", sender: tableView)
you need to make the sender as the row but not the tableview,so that the prepare for segue will get the sender as row instead of whole tableview.
As you are calling the prepareForSegue overtime you select a row, it makes sense to make the row as sender in performSegueWithIdentifier.
So it would be:
let row=indexPAth.row
performSegueWithIdentifier("showContent", sender: row)
I have a table in iOS. How to know what cell the user has clicked and pass information? I have been searching and I could find prepareForSegue. Is this the right method?. All the cases I could find were complicated and with a lot of elements. Can anyone apply to this simplified case and explain in a simple way, please. I am learning and for me is hard to understand this part.
let favoriteThings = [
"First",
"Second",
"Third",
]
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.favoriteThings.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// create a new instance of UITableViewCell. I give the name "cell" in Attributes > Identifier:
let cell = tableView.dequeueReusableCellWithIdentifier("FavoriteThingCell") as! UITableViewCell
var favoriteThingForRow = self.favoriteThings[indexPath.row]
cell.textLabel?.text = favoriteThingForRow
return cell
}
// How to know what cell was clicked and pass the right information? Is this the right method?:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// the second screen. I select the icon of View Controller and Attributes Inspector > Class and Storyboard ID is: DetallViewController
var secondScene = segue.destinationViewController as! DetallViewController
if let indexPath = self.tableView.indexPathForSelectedRow() {
let selected = favoriteThings[indexPath.row]
}
}
The usual way is to implement the table view delegate method tableView:didSelectRowAtIndexPath:. It's called by the runtime engine when the user taps a cell. In the method you can call performSegueWithIdentifier:sender: and pass the NSIndexPath instance as parameter sender.
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
performSegueWithIdentifier("MyIdentifier", sender: indexPath)
}
The method prepareForSegue:sender: is also called automatically right before the segue is performed to be able to setup things. As you have the selected index path you can retrieve the appropriate datasource item
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// the second screen. I select the icon of View Controller and Attributes Inspector > Class and Storyboard ID is: DetallViewController
var secondScene = segue.destinationViewController as! DetallViewController
let indexPath = sender as! NSIndexPath
let selected = favoriteThings[indexPath.row]
}