segue to detailed tableview controller - swift

I am passing data from one view controller to another. The first view controller has a collectionviewcell, and the controller I am segueing to has a tableviewcell. When I click on the cell of the collection view which is also clicking on a user, I want to segue into a detailed view controller that has a list of all the post the user made (which is stored in firebase)
Here is what I have so far: -This is my controller with the collectionviewcell
class WelcomeViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate{
var posts = NSMutableArray()
var databaseRef = FIRDatabase.database().reference()
var loggedInUser = FIRAuth.auth()?.currentUser
override func viewDidLoad() {
super.viewDidLoad()
loadData()
}
func loadData(){
if (FIRAuth.auth()?.currentUser) != nil{
FIRDatabase.database().reference().child("books").observeSingleEvent(of: .value, with: { (snapshot:FIRDataSnapshot) in
let loggedInUserData = snapshot
if let postsDictionary = snapshot .value as? [String: AnyObject] {
for post in postsDictionary {
self.posts.add(post.value)
}
self.CollectionView.reloadData()
}})}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "details" {
if segue.identifier == "UsersProfile" {
if let indexPath = sender as? IndexPath{
let vc = segue.destination as! UsersProfileViewController
let post = self.posts[indexPath.row] as! [String: AnyObject]
let posted = self.posts[indexPath.row] as! [NSMutableArray: AnyObject]
let username = post["username"] as? String
let userpicuid = post["uid"] as? String
let userpostuid = post["uid"] as? NSMutableArray
vc.username = username
vc.userpicuid = userpicuid
vc.posts = posts
print(indexPath.row)
}
}}
Right now when I click on the cells I segue into the detailed view controller and it displays all the posts made by all the users in my database and I know it is because in my segue I write vc.posts = posts. I am new to swift and I don't know how I will set it up so that when I click a cell (which is also when I click a user) then a detailed tableviewcontroller shows me all the post, only that user has made
Again, I am trying to only display the post uploaded by indiviual users

Overview:
A collection view displays a list of posts.
Each cell represents a post.
Each cell has a button with the user image when tapped on it, the app segues into UsersProfileViewController.
UsersProfileViewController contains a property posts which represents the posts made by the user.
Question:
How to set the UsersProfileViewController in such a way that it displays only the posts made by the selected user ?
Approach:
Each cell is dequeued and is reused.
When a button is tapped it needs to know which index path the button corresponds to.
Once the indexPath of the cell containing the button is known then we can determine the user associated with it.
Once the user is known filter the posts based on the selected user.
Steps:
Create a subclass of button with indexPath as a property
Use that custom button in your cell
In cellForItemAtIndexPath set the button indexPath
In cellForItemAtIndexPath set the target for the button
When the button is pressed, determine the corresponding username and store it as selectedUserName (view controller property).
In prepareForSegue filter posts based on the selectedUserName
Sample Code:
class CellButton : UIButton {
var indexPath : IndexPath?
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) as! CustomCollectionViewCell
let post = posts[indexPath.row] as? [String : Any]
cell.backgroundColor = .red
cell.titleLabel.text = post?["title"] as? String
cell.button.setTitle(post?["username"] as? String, for: .normal)
cell.button.addTarget(self, action: #selector(tappedOnUserButton(button:)),
for: .touchUpInside)
cell.button.indexPath = indexPath
return cell
}
#objc private func tappedOnUserButton(button: CellButton) {
guard let indexPath = button.indexPath else {
return
}
print("button pressed - \(indexPath)")
let post = posts[indexPath.row] as? [String : Any]
selectedUserName = post?["username"] as? String
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "UsersProfile" {
//Filter posts based on username
//Assumption: posts contains all the posts.
let postsForUser = posts.filter {
guard let post = $0 as? [String : Any] else {
return false
}
return post["username"] as? String == selectedUserName
}
print("postForUser = \(postsForUser)")
}
}
Preferred Approach:
Create a struct called User (instead of using a dictionary, which is error prone while coding)
Create a struct called Post, Post would have a property called User to map the user - https://itunes.apple.com/us/book/the-swift-programming-language-swift-3-1/id881256329?mt=11
Please learn Swift, each language has some features that you can take advantage of.
Don't force unwrap variables unless you are 100% sure it would contain a non-nil value
Use native types whenever possible (example use Swift Array instead of NSMutableArray)
Please explain your question clearly so that others don't have to spend time trying hard to understand. (Be precise and clear and isolate the problem, and use markdown for formatting)

You can setup segue from collectionViewController to tableViewController in your storyboard, then give it an identifier (very important).
In your collectionViewController, override prepareForSegue
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let vc = segue.destinationViewController as? YourTableViewControllerClassName {
// assign data you want to pass here
}
}
Then in your collectionView:didSelectItemAt method, call performSegueWithIdentifier("yourID", sender: self)
Hope this helps!

Related

How to fix delayed data on prepare segue?

My data got delayed 1 time when I want to pass the data in my tableview and pass it to another viewcontroller
i'm using prepare for segue.
now, in order to get the right data i need go back to the table view and press the same row
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toRequestCorrection"{
let destinationVC = segue.destination as! RequestCorrectionViewController
if let indexPath = tableView.indexPathForSelectedRow{
destinationVC.Shift = self.correction["Shift"].stringValue
destinationVC.LogDate = self.correction["LogDate"].stringValue
destinationVC.logIn = self.correction["ActualLogIn"].stringValue
destinationVC.logOut = self.correction["ActualLogOut"].stringValue
destinationVC.breakEnd = self.correction["ActualBreakEnd"].stringValue
destinationVC.breakStart = self.correction["ActualBreakStart"].stringValue
destinationVC.overTimeIn = self.correction["ActualOverTimeIn"].stringValue
destinationVC.overTimeOut = self.correction["ActualOverTimeOut"].stringValue
destinationVC.transactionStatusID = self.correction["TransactionStatusID"].intValue
}
}
}
it's should pass the data on the row right after i pressed the row
Additionally, you do not have to use segue, you can instantiate your view controller inside didSelectRowAt method with following code without prepareForSegue.
EDIT: You didn't indicate it's an async task. So, I am using Alamofire with completion handler. I think it will be useful for you.
typealias yourCompletionHandler = (Data?, _ message: String?, _ errorStatusCode: Int?) -> Void
func yourAsyncTask(handler: #escaping yourCompletionHandler) {
let parameters: [String: Any] = ["unitId": 4124]
let url = ""
Alamofire.request(url, method: .get, parameters: parameters)
.responseObject { (response: DataResponse<BaseListResponseParser<YourModel>>) in
switch response.result {
case .success:
if let result = response.result.value {
handler(result.listItems, nil, nil)
}
case .failure(let error):
print(error)
}
}
}
yourAsyncTask { [weak self] (yourModel, error, _) in
DispatchQueue.main.async {
guard let destination = UIStoryboard(name: "Main", bundle: nil)
.instantiateViewController(withIdentifier: "ViewControllerId") as? RequestCorrectionViewController else { return }
destination.Shift = yourModel.shift
navigationController?.pushViewController(destination, animated: true)
}
}
Using that way you do not need to create segue or prepareForSegue method.
You should implement tableVew(_:willSelectRowAt:) method of UITableViewDelegate in your viewController and set your correction property to selected row's value.
class YourSourceViewController: UITableViewDelegate{
var correction: Correction!
func tableView(_ tabelView: UITableView, willSelecRowAt indexPath: IndexPath) -> IndexPath?{
correction = correctionList[indexPath.row]
return indexPath
}
func prepare(for segue: UIStoryBoardSegue, sender: Any?){
//passing data to RequestCorrectionViewController.
//And also you don't need to check if indexPath is nil.
//Because this block will called only any row of tableView is selected.
}
}
Also note that you could do same thing in tableView(_:didSelectRowAt:) method but this method works after performing segues and cause the problem you are encountered too.
Edit
If you think using willSelectRowAt: method is a misusing, you can set your segue's source your viewController (instead of template cell) and set identifier on Storyboard and call it programmatically. like #vadian's said
func tableView(_ tabelView: UITableView, didSelecRowAt indexPath: IndexPath){
correction = correctionList[indexPath.row]
performSegue(withIdentifier "mySegueIdentifier", sender: self)
}

Segue to detailed view controller using a button in a cell

I have a collection view cell that passes data to a detailed view controller. When the cell is clicked, it segues into a view controller with more details. In the cells, I have a button, when the button is clicked, it also segues into a detailed view controller but a different view controller from when the cell is clicked.
This is what my didselect function looks like.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "details" {
self.navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.white]
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
let Authors = post["Author"] as? String
let ISBNS = post["ISBN"] as? String
let Prices = post["Price"] as? String
let imageNames = post["image"] as? String
let imagesTwo = post["imageTwo"] as? String
let imagesThree = post["imageThree"] as? String
let imagesFour = post["imageFour"] as? String
let imagesFive = post["imageFive"] as? String
vc.Booked = Booked
vc.Authors = Authors
vc.ISBNS = ISBNS
vc.Prices = Prices
vc.imageNames = imageNames
vc.imagesTwo = imagesTwo
vc.imagesThree = imagesThree
vc.imagesFour = imagesFour
vc.imagesFive = imagesFive
print(indexPath?.row)
} }
if segue.identifier == "UsersProfile" {
if let indexPaths = self.CollectionView!.indexPathsForSelectedItems{
let vc = segue.destination as! UsersProfileViewController
let cell = sender as! UICollectionViewCell
let indexPath = self.CollectionView!.indexPath(for: cell)
let post = self.posts[(indexPath?.row)!] as! [String: AnyObject]
let username = post["username"] as? String
let userpicuid = post["uid"] as? String
vc.username = username
vc.userpicuid = userpicuid
print(indexPath?.row)
}}}
For if the segue == User's Profile I get an error in the let cell = line. My button in the cell was created in the cellForItemAt collection view function
let editButton = UIButton(frame: CGRect(x: 106, y: 171, width: 36, height: 36))
editButton.addTarget(self, action: #selector(editButtonTapped), for: UIControlEvents.touchUpInside)
editButton.tag = indexPath.row
print(indexPath.row)
editButton.isUserInteractionEnabled = true
cell.addSubview(editButton)
When I click the cell, it works perfectly and segues me into a detailed view controller but when I click the button within the cell, I get an error.
Here is my editTappedButton function
#IBAction func editButtonTapped() -> Void {
print("Hello Edit Button")
performSegue(withIdentifier: "UsersProfile", sender: self)
}
It is obvious that you are getting that crash because with your button action you are calling performSegue(withIdentifier: "UsersProfile", sender: self) now with sender you are passing self means reference of current controller not the UICollectionViewCell what you need is get the indexPath of that cell and pass that and now in prepareForSegue cast the sender to IndexPath instead of UICollectionViewCell.
First replace your editButtonTapped with below one
#IBAction func editButtonTapped(_ sender: UIButton) -> Void {
print("Hello Edit Button")
let point = sender.superview?.convert(sender.center, to: self.tableView)
if let indexPath = self.tableView.indexPathForRow(at: point!) {
performSegue(withIdentifier: "UsersProfile", sender: indexPath)
}
}
Now in prepareForSegue for identifier UsersProfile cast the sender to IndexPath or simply replace your condition with my one.
if segue.identifier == "UsersProfile" {
if let indexPath = sender as? IndexPath{
let vc = segue.destination as! UsersProfileViewController
let post = self.posts[indexPath.row] as! [String: AnyObject]
let username = post["username"] as? String
let userpicuid = post["uid"] as? String
vc.username = username
vc.userpicuid = userpicuid
print(indexPath.row)
}
}

How to Push ViewController in UITableViewCell's #IBAction func

I have a UITableViewController and UITableViewCell and Cell's XIB Files.
My question is how to push UIViewController from cell?
in UITableViewCell.
#IBAction func pushBtnTapped(sender: AnyObject) {
// navigate to post view controller
let guest = self.storyboard?.instantiateViewControllerWithIdentifier("GuestCollectionVC") as! GuestCollectionVC
self.navigationController?.pushViewController(guest, animated: true)
}
I got an error message. self.storyboard is not UITableCell's member.
I know there is another way to implement this. But I have to implement in TableViewCell.
Ideally you'd push from the table view controller, in the didSelectRowAtIndexPath method call. Why do you say you have to implement it in the cell? There are other ways you could do that but from what you're saying this might be the best shot...
self.performSegueWithIdentifier("YourSegueId", sender: nil)
Then in prepareForSegue
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
//get the index path of the selected row
let indexPath = self.tableView.indexPathForSelectedRow
//just an example if you want to pass something to the other vc
let item = self.responseItems[indexPath!.row]
if segue.identifier == "YourSegueId" {
YourVC = segue.destinationViewController as! YourVC
YourVC.item = item
}
}
If you want to do it from the button you could do something like this in the prepareForSegue method... now you have the button and its index path
let button = sender as! UIButton
let view = button.superview
let cell = view?.superview as! YourCell
let indexPath = tableView.indexPathForCell(cell)

I want to send data when I tap a button in tableView Cell

I am implementing a commentView for my app. I have a main view which is tableview contains picture and a button to go comment view.
I want that when user tap comment button in table view, view shows comment view and pass PFObject by prepareforSegue method.
now comment button works, but I have an error from prepareforsegue
here is my code.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "mainToComment") {
let destViewController : CommentVC = segue.destinationViewController as! CommentVC
destViewController.parentObjectID = parentObjectID
let selectedRowIndex = self.tableView.indexPathForSelectedRow
destViewController.object = (postsArray[(selectedRowIndex?.row)!] as? PFObject)
and here is my how my button works.
#IBAction func commentButtonTapped(sender: AnyObject) {
let button = sender as! UIButton
let view = button.superview!
let cell = view.superview as! MainTVCE
let indexPath = tableView.indexPathForCell(cell)
parentObjectID = postsArray[(indexPath?.row)!].objectId!!
when I debug, selectedRowIndex has no value(nil)
I think it cause of I tap button instead of cell.
How can I set indexPath for this?
or
How can I make it work?
I don't know name of your main TableViewCell view controller. Assume that, I name this view controller is MainTableViewCell.
I create a closure in MainTableViewCell:
var didRequestToShowComment:((cell:UITableViewCell) -> ())?
When button comment is tapped:
#IBAction func commentButtonTapped(sender: AnyObject) {
self.didRequestToShowComment?(self) // self is this UITableViewCell
}
In table cellForRowAtIndex... of your main view controller.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
...
mainTableViewCell.didRequestToShowComment = { (cell) in
let indexPath = tableView.indexPathForCell(cell)
let objectToSend = postsArray[indexPath.row] as? PFObject
// Show your Comment view controller here, and set object to send here
}
...
return cell
}

prepareForSegue collectionView indexPath using swift

I don't find any good example to do what I want using swift so I allow myself to ask the question.
Im using a collectionView to display PFObjects and I want to send the displayed cell data to a second controller using prepareForSegue.
At this point, Im struggling to make this part of the code works:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "goto_answerquestion"){
var indexpath : NSIndexPath = self.collectionView.indexPathsForSelectedItems()
}
}
this line:
var indexpath : NSIndexPath = self.collectionView.indexPathsForSelectedItems()
triggers the following error:
(UICollectionView, numberOfItemsInSection: Int)-> Int does not have a member named 'indexPathsForSelectedItems'
please let me know if Im using the wrong method, or if you need additional data to have the appropriate overview of the problem.
ANSWER
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "segue_identifier"){
// check for / catch all visible cell(s)
for item in self.collectionView!.visibleCells() as [UICollectionViewCell] {
var indexpath : NSIndexPath = self.collectionView.indexPathForCell(item as CollectionViewCell)!
var cell : CollectionViewCell = self.collectionView!.cellForItemAtIndexPath(indexpath) as CollectionViewCell
// Grab related PFObject
var objectData:PFObject = self.questionData.objectAtIndex(indexpath.row) as PFObject
// Pass PFObject to second ViewController
let theDestination = (segue.destinationViewController as answerPageViewController)
theDestination.questionObject = objectData
}
}
}
If you just are trying to just find the index path of the cell tapped, and not have multiple, you could do this in your prepareForSegue method:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let indexPath = collectionView.indexPathForCell(sender as UICollectionViewCell)
// do what you need now with the indexPath
}
sender in this situation is the cell you tapped, so you just need to cast it to UICollectionViewCell (or a custom cell subclass, if you made one).
UPDATE:
Swift 1.2 introduced as! to replace as for this purpose, so to keep with safety, you can try this inside prepareForSegue using multiple bindings:
if let cell = sender as? UICollectionViewCell, indexPath = collectionView.indexPathForCell(cell) {
// use indexPath
}
This may Solve your problem :
var indexPath : NSArray = self.collectionView.indexPathsForSelectedItems()