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() }
Related
I have an app where users can like posts and I want to determine if the current user has previously liked a post in an efficient manner. My data currently looks like this:
I also store the likes for every user
In my current query I am doing this:
if let people = post["peopleWhoLike"] as? [String: AnyObject] {
if people[(Auth.auth().currentUser?.uid)!] != nil {
posst.userLiked = true
}
}
However, I believe this requires me to download all of the post's likes which isn't very efficient, so I tried this:
if (post["peopleWhoLike\(Auth.auth().currentUser!.uid)"] as? [String: AnyObject]) != nil {
posst.userLiked = true
}
The second method doesn't seem to be working correctly. Is there a better way to do this?
Here is my initial query as well:
pagingReference.child("posts").queryLimited(toLast: 5).observeSingleEvent(of: .value, with: { snap in
for child in snap.children {
let child = child as? DataSnapshot
if let post = child?.value as? [String: AnyObject] {
let posst = Post()
if let author = post["author"] as? String, let pathToImage = post["pathToImage"] as? String, let postID = post["postID"] as? String, let postDescription = post["postDescription"] as? String, let timestamp = post["timestamp"] as? Double, let category = post["category"] as? String, let table = post["group"] as? String, let userID = post["userID"] as? String, let numberOfComments = post["numberOfComments"] as? Int, let region = post["region"] as? String, let numLikes = post["likes"] as? Int {
Solved it:
In the tableView I just query for the liked value directly and then determine what button to display.
static func userLiked(postID: String, cell: BetterPostCell, indexPath: IndexPath) {
// need to cache these results so we don't query more than once
if newsfeedPosts[indexPath.row].userLiked == false {
if let uid = Auth.auth().currentUser?.uid {
likeRef.child("userActivity").child(uid).child("likes").child(postID).queryOrderedByKey().observeSingleEvent(of: .value, with: { snap in
if snap.exists() {
newsfeedPosts[indexPath.row].userLiked = true
cell.helpfulButton.isHidden = true
cell.notHelpfulButton.isHidden = false
}
})
likeRef.removeAllObservers()
}
}
}
Called in my TableView:
DatabaseFunctions.userLiked(postID: newsfeedPosts[indexPath.row].postID, cell: cell, indexPath: indexPath)
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 have a firebase database reference
FIRDatabase.database().reference().child("messages").child("\(self.convoId!)").childByAutoId()
I want to access the ReceiverName from my firebase database in DisplayViewController, but when I call the database reference, I get an error because of convoId. This is how convoId was declared (in MessagesViewController).
override func viewDidLoad() {
super.viewDidLoad()
let receiverId = receiverData as! String
let receiverIdFive = String(receiverId.characters.prefix(5))
let senderIdFive = String(senderId.characters.prefix(5))
if (senderIdFive > receiverIdFive)
{
self.convoId = senderIdFive + receiverIdFive
}
else
{
self.convoId = receiverIdFive + senderIdFive
}
}
receiverData was passed from UserviewController to MessagesViewController and "senderId" is the string identifier that uniquely identifies the current user sending messages and is automatically declared in JSQMessagesViewController.h. so essentially, I can't redeclare convoId in my DisplayViewController. However, in DisplayViewController, I need to access ReceiverName.
This is how I attempted to retrieve ReceiverName:
let rootRef = FIRDatabase.database().reference()
rootRef.child("messages").observeSingleEvent(of: .value) {
(snapshot: FIRDataSnapshot) in
let loggedInUserData = snapshot
if let postsDictionary = snapshot .value as? [String: AnyObject] {
for post in postsDictionary {
self.messages.add(post.value)
}
self.MessageTableView.reloadData()
}
}
Then I populate the derived data in a tableviewcell:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MessageCell", for: indexPath) as! MessageTableViewCell
//Configure the cell
print(messages[indexPath.row])
let message = self.messages[indexPath.row] as! [String: AnyObject]
cell.SellerName.text = message["ReceiverName"] as? String
return cell
}
Try this inside your observeSingleEvent function:
...
for post in postsDictionary {
let messages = post.value as! [String: AnyObject]
for (id, value) in messages {
let info = value as! [String: AnyObject]
let receiver = info["ReceiverName"]!
print("\(id): \(receiver)")
self.messages.add(value)
}
}
...
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)
})} } }}
My code is swift 3 and has create two array in TableView, get JSON data into Array, if I map this two array will get error index out of range, How to right map Array ?
func trainType() {
do {
/// 取得所有列車車種資料
let trainTypeUrl = URL(string: "http://ptx.transportdata.tw/MOTC/v2/Rail/TRA/TrainClassification?$format=JSON")
let trainTypeData = try? Data(contentsOf: trainTypeUrl!)
let trainTypeJson = try? JSONSerialization.jsonObject(with: trainTypeData!, options: .mutableContainers)
if let trainTypes = trainTypeJson as? [[String : AnyObject]] {
for dataTrainType in trainTypes {
trainTypeArray.append(dataTrainType as AnyObject)
}
}
}
self.tableView.reloadData()
}
func trainInOutstatusData() {
do {
let trainStatusUrl = URL(string: "http://ptx.transportdata.tw/MOTC/v2/Rail/TRA/LiveBoard?$format=JSON")
let trainInOutstatusData = try? Data(contentsOf: trainStatusUrl!)
let trainInOutStatusjson = try? JSONSerialization.jsonObject(with: trainInOutstatusData!, options: .mutableContainers)
if let InOutStatus = trainInOutStatusjson as? [[String : AnyObject]] {
for dataInOutStatus in InOutStatus {
trainStatusArray.append(dataInOutStatus as AnyObject!)
}
}
}
self.tableView.reloadData()
}
map array
let stationClassID = trainStatusArray[indexPath.row]["TrainClassificationID"] as? String
let trainClassID = trainTypeArray[indexPath.row]["TrainClassificationID"] as? String
if stationClassID == trainClassID {
if let trainTypeID = trainTypeArray[indexPath.row]["TrainClassificationID"] as? [String : Any] {
let ZHtw = trainTypeID["Zh_tw"] as? String
cell.stationTrainClassID.text = ZHtw
}
}
Since you have different data counts in both array, you can't do as you tried. Because consider you have 5 count in trainStatusArray and 3 count trainTypeArray, your current indexPath.row is 4. Then
let stationClassID = trainStatusArray[4]["TrainClassificationID"] as? String // You will get some value here.
let trainClassID = trainTypeArray[4]["TrainClassificationID"] as? String // Here app will crash because, total count is 3 in but you are trying to access element at index 4.
Solution:
You can give an array which have higher count as data source to tableView, then you can use for loop in cellForRowAtIndexpath to check the values are matching or not.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let stationClassID = trainStatusArray[indexPath.row]["TrainClassificationID"] as? String
for trainType in trainTypeArray {
let trainClassID = trainType["TrainClassificationID"] as? String
if stationClassID == trainClassID {
if let trainTypeID = trainTypeArray[indexPath.row]["TrainClassificationID"] as? [String : Any] {
let ZHtw = trainTypeID["Zh_tw"] as? String
cell.stationTrainClassID.text = ZHtw
}
}
}
}
Thanks.