I was able to create a UICollection View feed similar to Instagram but I am not sure how to select the cells and go to a more detailed view controller. Here are what my main view controller looks like. '
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
performSegue(withIdentifier: "details", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "details" {
if let indexPaths = self.collectionView!.indexPathsForSelectedItems{
let vc = segue.destination as! MainViewController
let indexPath = indexPaths[0] as NSIndexPath
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
vc.Booked = Booked
vc.Authors = Authors
vc.ISBNS = ISBNS
vc.Prices = Prices
vc.imageNames = imageNames
print(indexPath.row)
} }}
Here is what my database looks like:
//Detailed View Controller
FIRDatabase.database().reference().child("posts").child(self.loggedInUser!.uid).observeSingleEvent(of: .value, with: { (snapshot:FIRDataSnapshot) in
if let dictionary = snapshot .value as? [String: AnyObject] {
self.BookTitle.text = dictionary["title"] as? String
self.Author.text = dictionary["Author"] as? String
self.ISBN.text = dictionary["ISBN"] as? String
self.Price.text = dictionary["Price"] as? String
self.Image.image = ["image"] as? UIImage
}
})
Above is my detailed view controller. However, when I click the cells, my information is not passed
You need to give segue from cell instead of view.Like shown in image below
Then modify your code as shown below:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// No Need to call this performSegue(withIdentifier: "details", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "details" {
if let indexPaths = self.collectionView!.indexPathsForSelectedItems{
let vc = segue.destination as! MainViewController
let cell = sender as UICollectionViewCell
let indexPath = self.collectionView!.indexPathForCell(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
vc.Booked = Booked
vc.Authors = Authors
vc.ISBNS = ISBNS
vc.Prices = Prices
vc.imageNames = imageNames
print(indexPath.row)
} }}
then in DetailsViewController code will be like below (no need to reference firebase again as you already have all info) :
self.BookTitle.text = self.Booked
self.Author.text = self.Author
self.ISBN.text = self.ISBN
self.Price.text = self.Price
if let stringImage = self.imageNames as? String {
let imageRef = storage.reference(forURL: "gs://gsignme-14416.appspot.com/images/\(stringImage)")
imageRef.data(withMaxSize: 25 * 1024 * 1024, completion: { (data, error) -> Void in
if error == nil {
self.Image.image = UIImage(data: data!)
}else {
print("Error downloading image:" )
}
Write code in viewDidLoad.
This seems to be a duplicate. A great example of passing data between view controllers can be found here: Passing Data between View Controllers.
Structuring your data is also important. If you have a Post object received from your Firebase Database, you should create a local Post object and reference that. On the UICollectionViewCell, you can use the selected indexPath to get the Post designated to that cell.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "details" {
if let indexPaths = self.collectionView!.indexPathsForSelectedItems{
let vc = segue.destination as! MainViewController
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
if let imageNames = post["image"] as? String {
let imageRef = storage.reference(forURL: "gs://gsignme-14416.appspot.com/images/")
imageRef.data(withMaxSize: 25 * 1024 * 1024, completion: { (data, error) -> Void in
if error == nil {
let image = UIImage(data: data!)
}else {
print("Error downloading image:" )
}
vc.Booked = Booked
vc.Authors = Authors
vc.ISBNS = ISBNS
vc.Prices = Prices
vc.imageNames = imageNames
print(indexPath?.row)
})} } }}
Related
I have a view controller with a collection view, when the user click on the cell, the app segues into a view controller that has a table view of all the post the user has.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "userprofile" {
let vc = segue.destination as! UsersProfileViewController
let postsForUser: [[String: Any]] = posts.filter {
guard let post = $0 as? [String : Any] else {
return false
}
return post["uid"] as? String == uid
} as! [[String : Any]]
print("postForUser = \(postsForUser)")
vc.posts = postsForUser //vc.posts should be [[String: Any]]
}}
In the UsersProfileViewController the cells are populated in CellForRowAt
let post = self.posts![(indexPath?.row)!] as! [String: AnyObject]
cell.Title.text = post["title"] as? String
cell.Author.text = post["Author"] as? String
cell.ISBN10.text = post["ISBN10"] as? String
cell.ISBN13.text = post["ISBN13"] as? String
cell.CoverType.text = post["CoverType"] as? String
cell.Price.text = post["Price"] as? String
Everytime the view appears I want to reload the table view.
override func viewWillAppear(_ animated: Bool) {
self.navigationController?.isNavigationBarHidden = true
let indexPath = self.selectedIndex
let cell:ProfileTableViewCell? = TableView.cellForRow(at: indexPath! as IndexPath) as! ProfileTableViewCell
if(vcUserBook == true) // from EditPostViewController
{
let post = self.posts![(indexPath?.row)!] as! [String: AnyObject]
cell?.Title.text = post["title"] as? String
cell?.Author.text = post["Author"] as? String
cell?.ISBN10.text = post["ISBN10"] as? String
cell?.ISBN13.text = post["ISBN13"] as? String
cell?.CoverType.text = post["CoverType"] as? String
cell?.Price.text = post["Price"] as? String
}
}
But when I run it, the app crashes when I click on the collection cell.
You can´t initiate a cell as you do in your viewWillAppear. The tableView has not finished it´s drawing yet and that´s why you get the error.
All this part from viewWillAppear needs to be done in cellForRowAt:
let indexPath = self.selectedIndex
let cell:ProfileTableViewCell? = TableView.cellForRow(at: indexPath! as IndexPath) as! ProfileTableViewCell
if(vcUserBook == true) // from EditPostViewController {
let post = self.posts![(indexPath?.row)!] as! [String: AnyObject]
cell?.Title.text = post["title"] as? String
cell?.Author.text = post["Author"] as? String
cell?.ISBN10.text = post["ISBN10"] as? String
cell?.ISBN13.text = post["ISBN13"] as? String
cell?.CoverType.text = post["CoverType"] as? String
cell?.Price.text = post["Price"] as? String
}
In your viewWillAppear you should only do TableView.reloadData() and handle the cells in your cellForRowAt.
You can just reload data
override func viewWillAppear(_ animated: Bool) { self.navigationController?.isNavigationBarHidden = true
YourTableView.reloadData() }
I have this error:
unexpectedly found nil while unwrapping an Optional value"
at this line: vc.mainPreviewURL = posts[indexPath!].previewURL
HOW CAN I FIX THIS PLEASE?
This is all my code,thanks in advance ;)
class TableViewController: UITableViewController {
var posts = [post]()
var names = [String]()
var searchURL = "https://api.spotify.com/v1/search?q=Shawn+Mendes&type=track"
var oAuthToken = "BQCvqHzNOHyHgUTKvw43PdXV4yZs9jHvdIPsn3XbXNE5Jbg0zwNrpfwh81VMeuK5LQeRel0djaJT1IyLa1T9YzQmDypC5LkMD5z_NDzeAWRcEvH4fMc_nn50X2R_i8a38AMrjfMS8qPNhGYoHjAe8sFvjBSwQOereRr2RrEbmXc8JMGq7-Aq-ttalp87DuCRVy8mt8wVt8Muenihus8hXrctT071x7he2j_eGHJSWp7WoA5fOyk9xhzkxU_p_3Hkab6x6rbYCM4SFX9WlDtb5h_jikfehT-15Mjol_PmnRYo9WPnaCLKTs3AOblDlNk"
typealias JSONStandard = [String: AnyObject]
override func viewDidLoad() {
super.viewDidLoad()
callAlamo(url: searchURL,token: oAuthToken)
}
func callAlamo(url : String,token: String){
Alamofire.request(searchURL, method: .get, parameters: ["q":"Shawn Mendes", "type":"track"], encoding: URLEncoding.default, headers: ["Authorization": "Bearer "+oAuthToken]).responseJSON { response in
self.parseData(JSONData: response.data!)
print(response)
}
}
func parseData(JSONData:Data){
do {
var readableJSON = try JSONSerialization.jsonObject(with: JSONData, options: .mutableContainers) as! JSONStandard
print(readableJSON)
if let tracks = readableJSON["tracks"] as? JSONStandard{
if let items = tracks["items"] as? [JSONStandard] {
for i in 0..<items.count{
let item = items[i]
print(item)
let name = item["name"] as! String
let previewURL = item["preview_url"] as? String
if let album = item["album"] as? JSONStandard {
if let images = album["images"] as? [JSONStandard]{
let imageData = images [0]
let mainImageURL = URL(string: imageData["url"] as! String)
let mainImageData = NSData(contentsOf: mainImageURL!)
let mainImage = UIImage(data: mainImageData as! Data)
posts.append(post.init(mainImage: mainImage! ,name: name,previewURL: previewURL))
self.tableView.reloadData()
}
}
}
}
}
}
catch {
print(error)
}
}
override func prepare(for segue: UIStoryboardSegue,sender: Any?){
let indexPath = self.tableView.indexPathForSelectedRow?.row
let vc = segue.destination as! AudioVC
vc.image = posts[indexPath!].mainImage
vc.mainSongTitle = posts[indexPath!].name
vc.mainPreviewURL = posts[indexPath!].previewURL
}
}
Swift 3X
Change this line ...
override func prepare(for segue: UIStoryboardSegue,sender: Any?){
if let indexPath = self.tableView.indexPathForSelectedRow{
let vc = segue.destination as! AudioVC
vc.image = posts[indexPath.row].mainImage
vc.mainSongTitle = posts[indexPath.row].name
vc.mainPreviewURL = posts[indexPath.row].previewURL
}
}
You should declare your segue manual and call it from inside your didSelectRowAt function.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.performSegue(withIdentifier: "yourSegue",sender: indexPath.row)
}
override func prepare(for segue: UIStoryboardSegue,sender: Any?){
if let segue.identifier == "yourSegue" {
let vc = segue.destination as! AudioVC
let index = sender as! Int
vc.image = posts[index].mainImage
vc.mainSongTitle = posts[index].name
vc.mainPreviewURL = posts[index].previewURL
}
}
I want to take the title and put it as titleText in the DestVC which is a label. How do I put it in the segue function?
import UIKit
import Firebase
import FirebaseDatabase
import SDWebImage
struct postStruct {
let title : String!
let author : String!
let date : String!
let article : String!
let downloadURL : String!
}
class ZeroHomeViewController: UITableViewController {
var posts = [postStruct]()
var downloadURL : String = ""
override func viewDidLoad() {
super.viewDidLoad()
let ref = Database.database().reference().child("Posts")
ref.observeSingleEvent(of: .value, with: { snapshot in
print(snapshot.childrenCount)
for rest in snapshot.children.allObjects as! [DataSnapshot] {
guard let value = rest.value as? Dictionary<String,Any> else { continue }
guard let title = value["Title"] as? String else { continue }
guard let downloadURL = value["Download URL"] as? String else { continue }
guard let author = value["Author"] as? String else { continue }
guard let date = value["Date"] as? String else { continue }
guard let article = value["Article"] as? String else { continue }
let post = postStruct(title: title, author: author, date: date, article: article, downloadURL: downloadURL)
self.posts.append(post)
}
self.posts = self.posts.reversed(); self.tableView.reloadData()
})
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")
let label1 = cell?.viewWithTag(1) as! UILabel
label1.text = posts[indexPath.row].title
let imageView = cell?.viewWithTag(2) as! UIImageView
let post = self.posts[indexPath.row];
imageView.sd_setImage(with: URL(string: post.downloadURL), placeholderImage: UIImage(named: "placeholder"))
return cell!
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "detail" {
if let indexPath = tableView.indexPathForSelectedRow {
let destVC = segue.destination as! ArticleViewController
destVC.titleText = value["Title"] as? String
}
}
}
}
You simply need to access the relevant postStruct from your posts array and then get the title. You already have the index path for the selected row; the .row property will be the index in your posts array for the struct you need.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "detail" {
if let indexPath = tableView.indexPathForSelectedRow {
let destVC = segue.destination as! ArticleViewController
destVC.titleText = posts[indexPath.row].title
}
}
}
Recently I am trying to fetch data from Firebase and sending it to show another view controller's table view. But I have been failed due to bad handling of AsyncTask. Please suggest/show me the way how to solve this problem.
Here is my table in Firebase here. I want to get hotel name whose location is 'uttara'.
I tried a lot with googling but it does not work rightly. I am sure about my wrong placement AsyncTask and closure.
#IBAction func SearchActionInitiated(sender: UIButton)
{
dataTransfer(){ () -> () in
self.performSegueWithIdentifier("SearchResultPage", sender: self )
}
}
func dataTransfer(completed: backgroundTaskCompleted){
let BASE_URL_HotelLocation = "https://**************.firebaseio.com/Niloy"
let ref = FIRDatabase.database().referenceFromURL(BASE_URL_HotelLocation)
ref.queryOrderedByChild("location").queryStartingAtValue("uttara").observeEventType(.Value, withBlock: { snapshot in
if let result = snapshot.children.allObjects as? [FIRDataSnapshot] {
for child in result {
let downloadURL = child.value!["image"] as! String;
self.storage.referenceForURL(downloadURL).dataWithMaxSize(25 * 1024 * 1024, completion: { (data, error) -> Void in
let downloadImage = UIImage(data: data!)
let h = Hotel()
h.name = child.value!["name"] as! String
print("Object \(count) : ",h.name)
h.deal = child.value!["deal"] as! String
h.description = child.value!["description"] as! String
h.distance = child.value!["distance"] as! String
h.latestBooking = child.value!["latestBooking"] as! String
h.location = child.value!["location"] as! String
h.image = downloadImage!
self.HotelObjectArray.append(h)
dispatch_async(dispatch_get_main_queue(), {
completed();
});
})
}
}
else {
print("no results")
}
})
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "SearchResultPage")
{
let vc = segue.destinationViewController as! SearchResultPage
vc.arrayOfobject = HotelObjectArray
}
}
Where backgroundTaskCompleted is a closure.
I am making an app where you can search for movies with the API of http://www.omdbapi.com/.
The problem I am having is with the completion handler of dataTaskWithRequest. If you click on one of the collectionView cell, you will go to the detailView of that selected movie. However it doesn't work all the time. I get an error saying: unexpectedly found nil while unwrapping. And that's because it doesn't go in the completion handler of dataTaskWithRequest but goes straight to the detailVC and try passing data in the title label, genre label, etc but there is no data.
I hope you guys know what the problem is, because I have tried and I don't see what the problem is.
Or, does this problem occurs because of something before? Because first I retrieve data from http://www.omdbapi.com/ using "by search" instead of "by ID". And from there I retrieve the ID and from that ID I retrieve data for my detailVC.
Here is my code:
// Go to detail view of selected movie
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let selectedMovieId = self.IDs[indexPath.row]
chosenMovieId = selectedMovieId
self.performSegueWithIdentifier("showDetail", sender: self)
}
// Preparations before going to the detail view of selected movie
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetail" {
_ = self.movieInfoFrom(searchMovie: chosenMovieId, segue: segue)
}
}
func movieInfoFrom(searchMovie movieId: String, segue: UIStoryboardSegue) {
let movieUrlString = "http://www.omdbapi.com/?i=\(movieId)&y=&plot=full&r=json"
let url = NSURL(string: movieUrlString)
print(movieUrlString)
let urlRequest = NSURLRequest(URL: url!)
let urlSession = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
let urlTask = urlSession.dataTaskWithRequest(urlRequest) { (data, response, error) in
if error == nil {
// Convert data to JSON
let swiftyJSON = JSON(data: data!)
let title = swiftyJSON["Title"].string!
let runTime = swiftyJSON["Runtime"].string!
let genre = swiftyJSON["Genre"].string!
let plot = swiftyJSON["Plot"].string!
let rating = swiftyJSON["imdbRating"].string!
let year = swiftyJSON["Year"].string!
let poster = swiftyJSON["Poster"].string
self.infoResult = ["\(title)", "\(runTime)", "\(genre)", "\(plot)", "\(rating)", "\(year)"]
print("\(self.infoResult)")
let destinationVC = segue.destinationViewController as! MovieDetailController
destinationVC.movieDetails = self.infoResult
destinationVC.moviePoster = poster
}
}
urlTask.resume()
}
I tried to fix your code and explain with some comments:
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let selectedMovieId = self.IDs[indexPath.row]
chosenMovieId = selectedMovieId
self.movieInfoFrom(searchMovie: chosenMovieId)
}
// Preparations before going to the detail view of selected movie
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetail" {
let destinationVC = segue.destinationViewController as! MovieDetailController
destinationVC.movieDetails = self.infoResult
destinationVC.moviePoster = poster
}
}
func movieInfoFrom(searchMovie movieId: String) {
let movieUrlString = "http://www.omdbapi.com/?i=\(movieId)&y=&plot=full&r=json"
let url = NSURL(string: movieUrlString)
print(movieUrlString)
let urlRequest = NSURLRequest(URL: url!)
let urlSession = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
// This is asynchronously, you can put a loading here
let urlTask = urlSession.dataTaskWithRequest(urlRequest) { (data, response, error) in
// Got response, stop loading here
if error == nil {
// Convert data to JSON
let swiftyJSON = JSON(data: data!)
let title = swiftyJSON["Title"].string!
let runTime = swiftyJSON["Runtime"].string!
let genre = swiftyJSON["Genre"].string!
let plot = swiftyJSON["Plot"].string!
let rating = swiftyJSON["imdbRating"].string!
let year = swiftyJSON["Year"].string!
// You can save the poster as local variable
let poster = swiftyJSON["Poster"].string
self.infoResult = ["\(title)", "\(runTime)", "\(genre)", "\(plot)", "\(rating)", "\(year)"]
print("\(self.infoResult)")
// This should be call on main thread
dispatch_async(dispatch_get_main_queue()) {
self.performSegueWithIdentifier("showDetail", sender: self)
}
}
}
urlTask.resume()
}
Try this code, with safe optionals
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
chosenMovieId = self.IDs[indexPath.row]
movieInfoFrom(searchMovie: chosenMovieId)
}
func movieInfoFrom(searchMovie movieId: String) {
let movieUrlString = "http://www.omdbapi.com/?i=\(movieId)&y=&plot=full&r=json"
let url = NSURL(string: movieUrlString)
print(movieUrlString)
let urlRequest = NSURLRequest(URL: url!)
let urlSession = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
let urlTask = urlSession.dataTaskWithRequest(urlRequest) { (data, response, error) in
if error == nil && data != nil {
// Convert data to JSON
let swiftyJSON = JSON(data: data!)
let title = swiftyJSON["Title"].string ?? ""
let runTime = swiftyJSON["Runtime"].string ?? ""
let genre = swiftyJSON["Genre"].string ?? ""
let plot = swiftyJSON["Plot"].string ?? ""
let rating = swiftyJSON["imdbRating"].string ?? ""
let year = swiftyJSON["Year"].string ?? ""
let poster = swiftyJSON["Poster"].string
self.infoResult = ["\(title)", "\(runTime)", "\(genre)", "\(plot)", "\(rating)", "\(year)"]
print("\(self.infoResult)")
dispatch_async(dispatch_get_main_queue()) {
self.performSegueWithIdentifier("showDetail", sender: poster)
}
}
}
urlTask.resume()
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetail", let destinationVC = segue.destinationViewController as? MovieDetailController {
destinationVC.movieDetails = self.infoResult
destinationVC.moviePoster = sender as? String
}
}