How to access values in one class from another? Swift 4 - ios

I have an app where when a user taps a cell from a TableView representing a group of images, he is taken to another tableView where all the images within that group should be shown. However I am unsure of how to do this.
I have currently have extracted at the beginning the info necessary to make a reference and get all the data, which is in an array of objects. However how can I access these values from another class?
DataModel:
struct UserImage {
var userID: String
var image: UIImage
var postNum: String
}
I am creating an array of this as shown bellow, in a P1TableVC:
let arrayOfUserImageData = [UserImage]()
The function which retrieves and store the data looks as follows:
func fetchAllUserFristImage() {
print("Description: calling of fetchAllUserFristImage()")
Database.database().reference().child("Posts").observe(.childAdded, with: {(snapshot) in
if snapshot.value as? [String: AnyObject] != nil {
let user = snapshot.key
print("Description: calling of snapshot.value is not nil ")
self.databaseRef = Database.database().reference()
let usersPostRef2 = self.databaseRef.child("Posts").child(user)
usersPostRef2.observe(.value, with: {(postXSnapshots) in
if let postDictionary2 = postXSnapshots.value as? [String:AnyObject] {
for (p) in postDictionary2 {
if let posts = p.value as? [String:AnyObject] {
print("Description: posts has value of: \(posts)")
//to get back to where i was delete the below for i
for (i) in posts {
if let imageUrlString = i.value as? [String:AnyObject], let postUrl = imageUrlString["image1"] as? String {
print("Description: inside the if let imageUrlString = i.value ")
self.feedArray.append(Post(fetchedImageURL: postUrl))
if let imageUrl = URL(string: postUrl), let imageDataL = try? Data(contentsOf: imageUrl), let image = UIImage(data: imageDataL) {
print("Description: inside the if let imageUrl = URL(string: postUrl)")
print("Description: img url's of posts: \(imageUrl)")
self.tableData.append(UserImage(userID: user, image: image, postNum: p.key))
self.tableView.reloadData()
} else {print("this user had no posts, was nil")}
}
}
}
}
}
})
//below shud stay same
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
})
}

In didSelectItem method fetch the selected data from tableData based on indexPath.row and pass this data to next controller (make a variable in next controller and before pushing/presenting assign the fetched data to that variable).
didSelectItem : Fetch the selected data - let data = tableData[indexPath.row]
Assign it to next controller's variable -
let vc = nextVC()
vc.fetchedData = data
push/present vc

Or you may probably use Singleton
For instance: just create class Model
class Model {
static let sharedInstance = Model()
var tableVC: P1TableVC!
}
Then inside of your VC in viewDidLoad
class P1TableVC: UIViewController {
override func viewDidLoad() {
Model.sharedInstance.tableVC = self
}
}
Then you can use your class everywhere you want
Model.sharedInstance.tableVC.arrayOfUserImageData
Or you can create variable for all your data
class Model {
static let sharedInstance = Model()
var data: [UserImage]()
}
And then use data not VC
Model.sharedInstance.data

Related

Load data from firebase to tableview inside a collection cell

I have a collection view where you scroll vertically. Inside each collection view cell I have a tableview which consists of comments. I load data from Firebase to both the collection view and tableview.
What I'm trying to do is to load specific data and the correct number of rows In the tableview inside the collection cell based on the photoID from a photo in my collection cell. Basically I want the right comments and the right numberofrows to each tableview inside each collectionviewcell
The problem is that the comments doesn't appear in the right cells and also the numberofrows in the tableviews In the different collection view cells are not correct.
in my collectionviewcontroller I use this Func to get my data.
func fetchData(){
let ref = Database.database().reference()
ref.child("photos").observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject]{
let photo = Photo(dictionary: dictionary)
photo.videoId = snapshot.key
self.photos.append(photo)
if let photoId = photo.videoId{
self.photoTimeStampDictionary[photoId] = photo
}
DispatchQueue.main.async {
self.cView.reloadData()
}
self.attemptReloadCollectionView()
}
}, withCancel: nil)
}
then in cellforitem.
let photo = photos[indexPath.row]
cell.photo = photo
return cell
// I then use the photo variable inside my collectionviewcell.
This is my database structure:
**Comments**
**randomID**
[commenttext: "blabla"]
[uid: the senders UID]
[photo ID: the photoID]
**Photos**
**photo ID**
[and some more info here ofc]
My model class.
class Comment: NSObject {
var commentId: String?
var commentText: String?
var photoId: String?
var timeStamp: NSNumber?
var uid: String?
init(dictionary: [String: AnyObject]) {
self.commentId = dictionary["commentid"] as? String
self.commentText = dictionary["commenttext"] as? String
self.photoId = dictionary["photoid"] as? String
self.timeStamp = dictionary["timestamp"] as? NSNumber
self.uid = dictionary["uid"] as? String
}
}
The collection view works fine and just as it should.
This is what I have tried. (This is inside the collectionviewcell)
var comments = [Comment]()
func fetchData(){
let ref = Database.database().reference()
ref.child("comments").observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject]{
let comment = Comment(dictionary: dictionary)
// comment.commentId = snapshot.key
self.comments.append(comment)
DispatchQueue.main.async {
self.commentsTableView.reloadData()
}
}
}, withCancel: nil)
}
and then in cellforrow:
let comment = comments[indexPath.row]
if comment.photoId == self.photo?.videoId{
cell.commentText.text = comment.commentText
}
photo is my model class for all info connected to setting up the collection view cell except for the tableview. With the above code one specific comment that belongs to one specific picture ends up showing at several photos sometimes.
I would be grateful for help with this problem. I can imagine it has to do something with how you reload the tableview and also detect the index path in the collection view.
Thanks in advance.
Best regards,
Wurzel
Try removing the asynchronous func :
var comments = Comment
func fetchData(){
let ref = Database.database().reference()
ref.child("comments").observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject]{
let comment = Comment(dictionary: dictionary)
// comment.commentId = snapshot.key
self.comments.append(comment)
self.commentsTableView.reloadData()
}
}, withCancel: nil)
}
If that's indeed the issue, I will elaborate further.

How to append data in to NSMutableArray in ios swift

I'm working on an food ordering app currently when I add items into any array, then I need to pass that items to cartVC via CartDataModal where the cartArrayDict is of NSMutableArray type. But when I insert the SelectedDict the app crashes can anyone help me with this?
Heres my code:
var restMenu = [[String:Any]]()
func addTapped(cell: RestaurantItemViewCell)
{
//get the indexPath for add button click
let indexPath = self.restTableView.indexPath(for: cell)
print("the indexPath is", indexPath?.row)
print("all obj is",restMenu)
var selectedDict = restMenu[(indexPath?.row)!]
print("selected dict is",selectedDict)
selectedDict["ItemQuant"] = cell.itemQuantityLabel.text
print("selected dict is",selectedDict)
// Append In Cart Modal
CartDataModal.shared_Inst.cartArrayDict.insert(selectedDict, at: (indexPath?.row)!)
print("rest menu is",restMenu)
restMenu.remove(at: (indexPath?.row)!)
print("rest menu is",restMenu)
restMenu.insert(selectedDict, at: (indexPath?.row)!)
print("restmenu is",restMenu)
}
My CartDataModal:
class CartDataModal: NSObject {
static let shared_Inst = CartDataModal()
var cartArrayDict: [String:Any]!
}
from my ViewController i'm getting data From my Restaurant.plist
func moveToDetailController(img:UIImage,name:String)
{
print("the rest name is", name)
let pathUrl = Bundle.main.path(forResource: "ResturantFile", ofType: ".plist")
print("path url is",pathUrl as Any)
let finalArray = NSMutableArray(contentsOfFile: pathUrl!)
print("final Array is",finalArray)
let restaurantNames = finalArray?.firstObject as? NSDictionary
print("resturant name is",restaurantNames as Any)
if let menuDataArray:[[String:Any]] = restaurantNames?.value(forKey: name) as? [[String:Any]]
{
print("menu data is",menuDataArray)
let restVC = self.storyboard?.instantiateViewController(withIdentifier: "RestaurantViewController") as! RestaurantViewController
restVC.tempImg = img
restVC.tempTitle = name
restVC.restMenu = menuDataArray
self.navigationController?.pushViewController(restVC, animated: true)
}
}
You need to modify your struct:
class CartDataModal: NSObject {
static let shared_Inst = CartDataModal()
var cartArrayDict = [[String: Any]]()
}
You need to init array of the dictionary before insert. So You can init in struct or you can init before use according to your use.

Swift 3 Firebase retrieving key and passing to view controller

I've spend hours looking at identical questions but none of the answers I've found are helping this issue. Simple app retrieves data from Firebase Database and passes to another view controller from the tableview. The main data will pass through but I can't edit the information without an identifying "key" which I tried to set as childByAutoID() but then changed to a timestamp. Regardless of the method, all I get is the entries info not the actual key itself.
func loadData() {
self.itemList.removeAll()
let ref = FIRDatabase.database().reference()
ref.child(userID!).child("MyStuff").observeSingleEvent(of: .value, with: { (snapshot) in
if let todoDict = snapshot.value as? [String:AnyObject] {
for (_,todoElement) in todoDict {
let todo = TheItems()
todo.itemName = todoElement["itemName"] as? String
todo.itemExpires = todoElement["itemExpires"] as? String
todo.itemType = todoElement["itemType"] as? String
self.itemList.append(todo)
print (snapshot.key);
}
}
self.tableView.reloadData()
}) { (error) in
print(error.localizedDescription)
}
}
If your data looks like this:
Uid: {
MyStuff: {
AutoID: {
itemName: “Apocalypse”,
itemExpires: “December 21, 2012”,
itemType: “Catastrophic”
}
}
}
Then I would query like this:
ref.child(userID!).child("MyStuff").observeSingleEvent(of: .value, with: { (snapshot) in
for child in snapshot.children {
let child = child as? DataSnapshot
let key = child?.key as? String
if let todoElement = child?.value as? [String: Any] {
let todo = TheItems()
todo.itemName = todoElement["itemName"] as? String
todo.itemExpires = todoElement["itemExpires"] as? String
todo.itemType = todoElement["itemType"] as? String
self.itemList.append(todo)
self.tableView.reloadData()
}
}
})
Additionally, like I said in my comment you can just upload the key with the data if you’re using .updateChildValues(). Example:
let key = ref.child("userID!").childByAutoId().key
let feed = ["key": key,
“itemName”: itemName] as [String: Any]
let post = ["\(key)" : feed]
ref.child("userID").child("MyStuff").updateChildValues(post) // might want a completionBlock
Then you can get the key the same way you are getting the rest of the values. So your new data would look like this:
Uid: {
MyStuff: {
AutoID: {
itemName: “Apocalypse”,
itemExpires: “December 21, 2012”,
itemType: “Catastrophic”,
key: “autoID”
}
}
}
The key you are trying to look for is located in the iterator of your for loop
Inside your if-let, try to do this:
for (key,todoElement) in todoDict {
print(key) // this is your childByAutoId key
}
This should solve the problem. Otherwise show us a screen of your database structure

How to retrieve firebase database properly?

I am trying to retrieve the data from firebase database. However, I cannot get my local variables assigned to the values of the database. I am using the following classes and methods.
class Episode {
var title: String?
var description: String?
var location: String?
var discount: String?
var star: Int?
init() {
self.title = ""
self.description = ""
self.location = ""
self.discount = ""
self.star = 0
}
This is my method for pulling the data from the databse
func getValues() -> Episode {
let rootRef = FIRDatabase.database().reference().child("Restaurants").child("The Kafe")
let descriptionRef = rootRef.child("Description")
let discountRef = rootRef.child("Discount")
let locationRef = rootRef.child("Location")
let starRef = rootRef.child("Star")
let episode = Episode()
descriptionRef.observeEventType(.Value) { (snap: FIRDataSnapshot) in
episode.description = snap.value as? String
}
discountRef.observeEventType(.Value) { (snap: FIRDataSnapshot) in
episode.discount = snap.value as? String
}
locationRef.observeEventType(.Value) { (snap: FIRDataSnapshot) in
episode.location = snap.value as? String
}
starRef.observeEventType(.Value) { (snap: FIRDataSnapshot) in
episode.star = snap.value as? Int
print(episode.description!)
}
return episode
}
When I print out the values of the returned episode, they are all empty. However, when I print the values within the closure itself (Eg. if I do print(episode.description) within the obserEventType closure, it works fine. But if I print it outside it is empty.
I think I am missing something fundamental about swift or firebase. I am new to iOS programming so any help would be greatly appreciated.
Only inside the first observer you will have the value the return will always be nil, that is because only the return is trying to work in a sync way while firebase will always work in an async way
rootRef.observeEventType(.Value, withBlock: {(snap) in
let ep: Dictionary<String,AnyObject?> = [
"title": snap.childSnapshotForPath("Title").value as? String,
"description": snap.childSnapshotForPath("Description").value as? String,
"location": snap.childSnapshotForPath("Location").value as? String,
"discount": snap.childSnapshotForPath("Discount").value as? String,
"star": (snap.childSnapshotForPath("Star").value as? NSNumber)?.integerValue,
]
//Here you have your data in your object
episode = Episode(key: snap.key, dictionary: ep)
})
rootRef.observeEventType(.Value) { (snap: FIRDataSnapshot) in
print(snap.childSnapshotForPath("Title").value as? String)
}
return episode!
Also if you want to get it from a function like that you should probably use observeSingleEventType.
You need to rethink flow of your code because you are expecting firebase to work synchronously when it is always asynchronous. The way you have your getValues function will never work.
To solve this issue you should read about async execution and callbacks in swift.
All Firebase events are asynchronous so they are executed in a non-sequential way, that is why you only have access to the data inside the context of the callback...if you put a print outside the callback it is executed in a synchronous way so it gets executed before the callback, that is why it is in its initial status
1) You only need the rootRef, delete the rest
let ref = FIRDatabase.database().reference().child("Restaurants").child("The Kafe")
2) You only need one observer
var episode:Episode? = nil
rootRef.observeEventType(.Value,withBlock: {(snap) in
let ep:Dictionary<String,AnyObject?> = [
"title":snap.childSnapshotForPath("title").value as? String,
//Etc...
"star":(snap.childSnapshotForPath("price").value as? NSNumber)?.integerValue,
]
//Here you have your data in your object
episode = Episode(key:snap.key,dictionary:ep)
}
3) your episode class can be like this
class Episode {
private var _key:String!
private var _title:String?
//Etc.....
private var _star:Int?
var key:String!{
return _key
}
var title:String?{
return _title
}
//Etc....
var star:Int?{
return _star
}
init(key:String!, title:String?,//etc...., star:Int?){
self._key = key
self._title = title
//Etc....
}
init(key:String,dictionary:Dictionary<String,AnyObject?>){
_key = key
if let title = dictionary["title"] as? String{
self._title = title
}
//Etc...
if let star = dictionary["star"] as? Int{
self._star = star
}
..
}
}

Saving custom NSObject in NSUserDefaults

I'm having a modal entity file as below,
import UIKit
class MyProfile: NSObject {
var userName : String = ""
func initWithDict(dict: NSMutableDictionary) {
self.userName = dict.objectForKey("username") as! String
}
}
Saving that entity by encoding as below,
let myDict: NSMutableDictionary = ["username": "abc"]
let myEntity:MyProfile = MyProfile()
myEntity.initWithDict(myDict)
let userDefaults = NSUserDefaults.standardUserDefaults()
let encodedData = NSKeyedArchiver.archivedDataWithRootObject(myEntity)
userDefaults.setObject(encodedData, forKey: "MyProfileEntity")
userDefaults.synchronize()
Getting that saved entity as below,
let myEntity:MyProfile = MyProfile()
let userDefaults = NSUserDefaults.standardUserDefaults()
guard let decodedNSData = userDefaults.objectForKey("MyProfileEntity") as? NSData,
myEntity = NSKeyedUnarchiver.unarchiveObjectWithData(decodedNSData) as? MyProfile!
else {
print("Failed")
return
}
print(myEntity.userName)
It's not working, having crashes and lot of syntax errors, I'm new to swift,
It's showing some syntax errors like definition conflicts with previous value in the unarchiveObjectWithData line. If I fix that error, then at the time of getting the entity from userdefaults it's crashing.
can anyone suggest how can I resolve it?
To save custom object into user default, you must implement NSCoding protocol. Please replace your custom data model like this:
class MyProfile: NSObject,NSCoding {
var userName : String = ""
#objc required init(coder aDecoder:NSCoder){
self.userName = aDecoder.decodeObjectForKey("USER_NAME") as! String
}
#objc func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(self.userName, forKey: "USER_NAME")
}
init(dict: [String: String]) {
self.userName = dict["username"]!
}
override init() {
super.init()
}
}
Here is the code for saving and retrieving MyProfile object:
// Save profile
func saveProfile(profile: MyProfile){
let filename = NSHomeDirectory().stringByAppendingString("/Documents/profile.bin")
let data = NSKeyedArchiver.archivedDataWithRootObject(profile)
data.writeToFile(filename, atomically: true)
}
// Get profile
func getProfile() -> MyProfile?{
if let data = NSData(contentsOfFile: NSHomeDirectory().stringByAppendingString("/Documents/profile.bin")){
let unarchiveProfile = NSKeyedUnarchiver.unarchiveObjectWithData(data) as! MyProfile
return unarchiveProfile
} else{
return nil
}
}
Now here is the code snippet how to use those method:
// Create profile object
let profile = MyProfile(dict: ["username" : "MOHAMMAD"])
// save profile
saveProfile(profile)
// retrieve profile
if let myProfile = getProfile(){
print(myProfile.userName)
}else{
print("Profile not found")
}
You can't do this:
let myEntity:MyProfile = MyProfile()
Then later on, do this:
myEntity = ...
When something is defined with 'let', you cannot change it.
Change to
var myEntity: MyProfile?
It is possible that
NSKeyedUnarchiver.unarchiveObjectWithData(decodedNSData)
is returning nil. You then proceed to force unwrapping by adding
as? MyProfile!
try changing this to
as? MyProfile
Then later, see if you got something back
if let myEntity = myEntity {
print(myEntity.userName)
}

Resources