Segue to detailed view controller using a button in a cell - swift

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)
}
}

Related

How do you pass data from tableview cell button with a UILongPressGestureRecognizer?

I have a button in my cell that if the user holds for a certain length of time it will trigger a popup. I am having trouble passing the cell data with the long press button.
Heres how I submit and pass data with a regular tap...
cell.addButton.tag = (indexPath as NSIndexPath).row
cell.addButton.addTarget(self, action: #selector(Dumps.addAction(_:)), for: UIControl.Event.touchUpInside)
.
#IBAction func addAction(_ sender: Any) {
let tag = (sender as AnyObject).tag
let cell = tableView.cellForRow(at: IndexPath.init(row: tag!, section: 0)) as! DumpsCell01
codeData = cell.codeField.text! }
The above works fine.
Heres how I submit the button with the long press gesture. Its passing nil through _sender I think
cell.deleteButton.tag = (indexPath as NSIndexPath).row
let longGesture = UILongPressGestureRecognizer(target: self, action: #selector(Dumps.deleteAction(_:)))
cell.deleteButton.addGestureRecognizer(longGesture)
.
#objc func deleteAction(_ sender: UIGestureRecognizer){
let tag = (sender as AnyObject).tag
let cell = tableView.cellForRow(at: IndexPath.init(row: tag!, section: 0)) as! DumpsCell01
cell.codeLabel.backgroundColor = UIColor.red }
How would I pass the data through this method?
You should be using the tag of the UIButton instead of the UILongPressGestureRecognizer as you have done above.
func deleteAction(_ sender: UILongPressGestureRecognizer) {
guard let tag = (sender.view as? UIButton)?.tag else { return }
let cell = tableView.cellForRow(at: IndexPath(row: tag, section: 0)) as? DumpsCell01
cell?.codeLabel.backgroundColor = .red
}
Note: I've also avoided force unwrapping as you should too through-out the project.

How to view the UITableView from the top and not bottom Xcode (Social Media App)

I am trying to make a social media feed/ wall using the UITableView with custom cells (cells called PostTableViewCell.xib) I am using this through Firebase Database. Currently I have a view controller for the feed (called HomeViewController.swift) and a separate view controller for the user to post (called NewPostViewController.swift)
My problem is every time a user made a post it would show up at the very bottom of the table and not the top like instagram, twitter, facebook would do. I was able to get around this by flipping the UiTableView and its cells 180 degrees, but now it loads that home view controller at the bottom of all the posts but the cells are now in the right order.
How can I start this home page at the top of these cells while having new cells come in at the top too. Attached is the necesary code on that homeviewcontroller:
import Foundation
import UIKit
import Firebase
class HomeViewController:UIViewController, UITableViewDelegate, UITableViewDataSource {
var tableView:UITableView!
var posts = [Post]()
override func viewDidLoad() {
super.viewDidLoad()
tableView = UITableView(frame: view.bounds, style: .plain)
let cellNib = UINib(nibName: "PostTableViewCell", bundle: nil)
tableView.register(cellNib, forCellReuseIdentifier: "postCell")
tableView.backgroundColor = UIColor(white: 0.90,alpha:1.0)
view.addSubview(tableView)
var layoutGuide:UILayoutGuide!
if #available(iOS 11.0, *) {
layoutGuide = view.safeAreaLayoutGuide
} else {
// Fallback on earlier versions
layoutGuide = view.layoutMarginsGuide
}
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.leadingAnchor.constraint(equalTo: layoutGuide.leadingAnchor).isActive = true
tableView.topAnchor.constraint(equalTo: layoutGuide.topAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: layoutGuide.trailingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: layoutGuide.bottomAnchor).isActive = true
tableView.delegate = self
tableView.dataSource = self
tableView.reloadData()
observePosts()
//Flips UITableView
tableView.transform = CGAffineTransform(rotationAngle: (-.pi))
}
func observePosts() {
let postsRef = Database.database().reference().child("posts")
postsRef.observe(.value, with: { snapshot in
var tempPosts = [Post]()
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 photoURL = author["photoURL"] as? String,
let url = URL(string:photoURL),
let text = dict["text"] as? String,
let timestamp = dict["timestamp"] as? Double {
let userProfile = UserProfile(uid: uid, username: username, photoURL: url)
let post = Post(id: childSnapshot.key, author: userProfile, text: text, timestamp:timestamp)
tempPosts.append(post)
}
}
self.posts = tempPosts
self.tableView.reloadData()
})
}
#IBAction func handleLogout(_ sender:Any) {
try! Auth.auth().signOut()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "postCell", for: indexPath) as! PostTableViewCell
cell.set(post: posts[indexPath.row])
//Flips cell
cell.transform = CGAffineTransform(rotationAngle: (-.pi))
return cell
}
}
The reason it is appearing at the bottom is because you are appending it here:
tempPosts.append(post)
Append puts the new element at the end of the array. You should be inserting it at the first position like so:
tempPosts.insert(post, at: 0)
Alternatively, you should be sorting the data that you are serving by the date created. Create a variable:
_posts: [Post] {
get {
return self.posts.sorted(by: { $0.timestamp > $1.timestamp } )
}
}
and then use your _posts variable.

segue to detailed tableview controller

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!

Search bar will only show first Cell in detail view

I have created a TableView with a Search Bar in Swift.
I have created 3 searchable objects. (131, 132, 137)
When i search for 137 and press 137(which is the only option to press), i still get to 131 detailView. Anyone know why? Thanks
Here is my code:
import UIKit
class TableViewController: UITableViewController, UISearchResultsUpdating {
let TitleList = ["131","132","137"]
override func viewDidLoad() {
super.viewDidLoad()
self.resultSearchController = UISearchController(searchResultsController: nil)
self.resultSearchController.searchResultsUpdater = self
self.resultSearchController.dimsBackgroundDuringPresentation = false
self.resultSearchController.searchBar.sizeToFit()
self.tableView.tableHeaderView = self.resultSearchController.searchBar
self.tableView.reloadData()
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: TableViewCell = tableView.dequeueReusableCellWithIdentifier("Cell") as! TableViewCell
cell.LabelTitle.text = TitleList[indexPath.row]
cell.CellDescription.text = DescriptionList[indexPath.row]
let imagename = UIImage(named: ImageList[indexPath.row])
cell.CellImage.image = imagename
if self.resultSearchController.active {
cell.LabelTitle?.text = self.filteredfarger[indexPath.row]
}
else {
cell.LabelTitle?.text = self.TitleList[indexPath.row]
}
return cell
}
func updateSearchResultsForSearchController(searchController: UISearchController) {
self.filteredfarger.removeAll(keepCapacity: false)
let searchPredicate = NSPredicate(format: "SELF CONTAINS[c] %#", searchController.searchBar.text!)
let array = (self.TitleList as NSArray).filteredArrayUsingPredicate(searchPredicate)
self.filteredfarger = array as! [String]
self.tableView.reloadData()
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "DetailView") {
let VC = segue.destinationViewController as! DetailedViewController
if let indexpath = self.tableView.indexPathForSelectedRow {
let Title = TitleList[indexpath.row] as String
VC.SentData1 = Title
}
Because when you do the search the row for 137 is 0, and then in prepareForSegue you use the tableView.indexPathForSelectedRow (which means indexPath.row == 0) to get the title data. Try doing something like in your tableView.cellForRowAtIndexPath function where you check if resultSearchController is active or not, and if it is use filteredfarger instead of TitleList. Something like this maybe:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "DetailView") {
let VC = segue.destinationViewController as! DetailedViewController
if let indexpath = self.tableView.indexPathForSelectedRow {
if self.resultSearchController.active {
let Title = self.filteredfarger[indexpath.row]
VC.SentData1 = Title
}
else {
let Title = TitleList[indexpath.row] as String
VC.SentData1 = Title
}
}
}
}
Better yet, you already give the cell some info, why not just use that, kinda like this:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "DetailView") {
let VC = segue.destinationViewController as! DetailedViewController
if let cell = sender as? TableViewCell {
VC.SentData1 = cell.LabelTitle.text
}
}
}

can't retrieve indexPath on prepareforsegue?

I'm having a problem with retrieving the indexPath on prepareForSegue with the "comments" block. The comments button is inside the cell, and I created a segue from the view controller to the desired vc but every time I click the button, I get an "unexpectedly found nil while unwrapping an optional value" on the part where I declare the indexPath.
override func shouldPerformSegueWithIdentifier(identifier: String?, sender: AnyObject?) -> Bool {
if (identifier == "webPage1") {
let indexPath: NSIndexPath = tableView.indexPathForSelectedRow()!
if (arrayByVotes[indexPath.row].objectForKey("videoURL") as NSString == "") {
performSegueWithIdentifier("comments", sender: nil)
return false
}
}
return true
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "webPage1") {
var urlToOpen:String = ""
let indexPath: NSIndexPath = tableView.indexPathForSelectedRow()!
if((arrayByVotes[indexPath.row].objectForKey("videoURL")) != nil) {
urlToOpen = arrayByVotes[indexPath.row].objectForKey("videoURL") as String
urlToOpen = urlToOpen.stringByReplacingOccurrencesOfString("http:", withString: "https:", options: .LiteralSearch, range: nil)
}
let sW: webpageViewController = segue.destinationViewController as webpageViewController
sW.urlPath = urlToOpen
} else if (segue.identifier == "comments") {
let indexPath: NSIndexPath = tableView.indexPathForSelectedRow()!
let sweet:PFObject = arrayByVotes[indexPath.row] as PFObject
var postId = sweet.objectId
let selectedFContent: String = arrayByVotes[indexPath.row].objectForKey("content") as String
let fpvc: FeedPageViewController = segue.destinationViewController as FeedPageViewController
fpvc.selectedFeedFeedContent = selectedFContent
fpvc.ourpostId = postId
}
}
I then tried wrapping it in an
if let indexPath = tableView.indexPathForSelectedRow()!
but that didn't work either as it wouldn't retrieve the correct indexPath every time.
Essentially the program is supposed to check to see if a cell contains a URL. If it does, it calls the vc with a webview, and if not, then it directly calls the comments page.
Try this
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "webPage1") {
let selectedRow = tableView.indexPathForSelectedRow()?.row
var urlToOpen = arrayByVotes(selectedRow!) as? String
if((urlToOpen != nil) {
urlToOpen = urlToOpen.stringByReplacingOccurrencesOfString("http:", withString: "https:", options: .LiteralSearch, range: nil)
}
let sW: webpageViewController = segue.destinationViewController as webpageViewController
sW.urlPath = urlToOpen
} else if (segue.identifier == "comments") {
let selectedRow = tableView.indexPathForSelectedRow()?.row
let sweet:PFObject = arrayByVotes(selectedRow!) as PFObject
var postId = sweet.objectId
let selectedFContent: String = arrayByVotes(selectedRow!).objectForKey("content") as String
let fpvc: FeedPageViewController = segue.destinationViewController as FeedPageViewController
fpvc.selectedFeedFeedContent = selectedFContent
fpvc.ourpostId = postId
}
}