what i want to do here is when i access the first album by pressing on the picture i should get pictures from firebase for that specific album but i have a problem with that and here is my code.
class albumsVC: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource{
#IBOutlet weak var collectionView: UICollectionView!
var posts = [Post]()
static var imageCache: NSCache<NSString, UIImage> = NSCache()
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
DataService.ds.REF_POSTS3.observe(.value, with: { (snapshot) in
if let snapshot = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshot {
print ("SNAP: \(snap)")
if let postDict = snap.value as? Dictionary<String, AnyObject>{
let key = snap.key
let post = Post(postKey: key , postData: postDict)
self.posts.append(post)
}
}
}
self.collectionView.reloadData()
})
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return posts.count
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let post = posts[indexPath.row]
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)as? collectionViewCellAlbums {
if let img = albumsVC.imageCache.object(forKey: post.mainImg as NSString) {
cell.configureCell(post: post, img: img)
return cell
}else {
cell.configureCell(post: post)
return cell
}
}
else {
return collectionViewCellAlbums()
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.performSegue(withIdentifier: "showAlbum", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
/**
if segue.identifier == "showAlbum"
{
let indexPaths = self.collectionView!.indexPathsForSelectedItems!
let indexPath = indexPaths[0] as IndexPath
let vc = segue.destination as! newViewController
let post = posts[indexPath.row]
if let img = CollectionVC.imageCache.object(forKey: post.imageUrl as NSString) {
vc.image = img
}
}
*/
}
}
I don't know how to access the array in the second class as shown in the picture for this function override func prepare(for segue
the class for the second array is exactly the same as the first one except the newViewController.swift
class newViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
var image = UIImage()
override func viewDidLoad() {
super.viewDidLoad()
self.imageView.image = self.image
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
In class collectionVC you should have variable :
imageList
and in albumVC at:
prepare(for segue: UIStoryboardSegue, sender: Any?)
after
let vc = segue.destination as! newViewController
set :
vc.imageList = self.post.getAllImageOfPost()
In such cases using a singleton class solves the problem. In Singleton pattern you create instance just-in-time. This means when you set an array for example, this array will be allocated in memory until the program finishes running.
Refer to this link for Singleton implementation:
http://www.galloway.me.uk/tutorials/singleton-classes/
Related
I am working on incorporating a Firestore repository into a SwiftUI 2 app. The list view loads appropriately, and refreshes automatically when the data in the Firestore collection is changed. A NavigationLink loads the appropriate DetailView correctly. A button on the DetailView is set to change data within the relevant document, and works correctly, but the DetailView page does not refresh with the correct data (it appears correctly when exiting to the list view and returning); likewise the DetailView will not respond to changes to the collection made elsewhere.
I have attempted to use various configurations of ObservedObject and State, but have been unable to get the result intended.
Any help would be appreciated. Snipped Code listed below shows the flow, and uses the status field as an example.
CustomerRepository
class CustomerRepository: ObservableObject {
private let path: String = "customers"
private let store = Firestore.firestore()
#Published var customers: [Customer] = []
private var cancellables: Set<AnyCancellable> = []
init() {
store.collection(path)
.addSnapshotListener { querySnapshot, error in
if let error = error {
print("Error getting customers \(error.localizedDescription)")
return
}
self.customers = querySnapshot?.documents.compactMap { document in
try? document.data(as: Customer.self)
} ?? []
}
}
func update(_ customer: Customer) {
guard let customerID = customer.id else { return }
do {
try store.collection(path).document(customerID).setData(from: customer)
} catch {
fatalError("Unable to update record: \(error.localizedDescription)")
}
}
}
CustomerListViewModel
class CustomerListViewModel: ObservableObject {
#Published var customerViewModels: [CustomerViewModel] = []
private var cancellables: Set<AnyCancellable> = []
#Published var customerRepository = CustomerRepository()
init() {
customerRepository.$customers.map { customers in
customers.map(CustomerViewModel.init)
}
.assign(to: \.customerViewModels, on: self)
.store(in: &cancellables)
}
}
CustomerViewModel
class CustomerViewModel: ObservableObject, Identifiable {
private let customerRepository = CustomerRepository()
#Published var customer: Customer
private var cancellables: Set<AnyCancellable> = []
var id = ""
init(customer: Customer) {
self.customer = customer
$customer
.compactMap { $0.id }
.assign(to: \.id, on: self)
.store(in: &cancellables)
}
func update(customer: Customer) {
customerRepository.update(customer)
}
}
CustomerListView
struct CustomerListView: View {
#ObservedObject var customerListViewModel = CustomerListViewModel()
var body: some View {
NavigationView {
List {
ForEach(customerListViewModel.customerViewModels) { customerViewModel in
NavigationLink(
destination: CustomerDetailView(customerViewModel: customerViewModel)) {
CustomerCell(customerViewModel: customerViewModel)
}
}
}
}
}
}
CustomerDetailView
struct CustomerDetailView: View {
var customerViewModel: CustomerViewModel
var body: some View {
VStack {
Text(customerViewModel.customer.status.description)
}.onTapGesture {
nextTask()
}
}
private func nextTask() {
switch customerViewModel.customer.status {
case .dispatched:
customerViewModel.customer.status = .accepted
...
default:
return
}
update(customer: customerViewModel.customer)
}
func update(customer: Customer) {
customerViewModel.update(customer: customer)
}
}
After some reconfiguring, and the help of this example:
https://peterfriese.dev/swiftui-firebase-update-data/
I was able to solve my issue. Below is a breakdown of the revised code in case it will help others....
CustomerViewModel
class CustomerViewModel: ObservableObject, Identifiable {
#Published var customer: Customer
#Published var modified = false
private var cancellables: Set<AnyCancellable> = []
init(customer: Customer = Customer(status: .new)) {
self.customer = customer
self.$customer
.dropFirst()
.sing { [weak self] customer in
self?.modified = true
}
.store(in: &cancellables)
}
func handleDoneTapped() {
self.updateOrAddCustomer()
}
}
CustomerListView
// Snip to Navigation Link
NavigationLink(
destination: CustomerDetailView(customerViewModel: CustomerViewModel(customer: customerViewModel.customer))) {
CustomerCell(customerViewModel: customerViewModel)
}
CustomerDetailView
struct CustomerDetailView: View {
#ObservedObject var customerViewModel = CustomerViewModel()
var body: some View {
VStack {
Text(customerViewModel.customer.status.description)
}.onTapGesture {
nextTask()
}
}
private func nextTask() {
switch customerViewModel.customer.status {
case .dispatched:
customerViewModel.customer.status = .accepted
...
default:
return
}
update(customer: customerViewModel.customer)
}
func update(customer: Customer) {
customerViewModel.handleDoneTapped()
}
}
Here you need to put the parameters for which values being updated and want the UI to update accordingly:
class CustomerViewModel: ObservableObject, Identifiable {
private let customerRepository = CustomerRepository()
#Published var customer: Customer
private var cancellables: Set<AnyCancellable> = []
var id = ""
var status: StatusEnum
init(customer: Customer) {
self.customer = customer
self.status = customer.status
$customer
.compactMap { $0.status }
.assign(to: \.status, on: self)
.store(in: &cancellables)
$customer
.compactMap { $0.id }
.assign(to: \.id, on: self)
.store(in: &cancellables)
}
func update(customer: Customer) {
customerRepository.update(customer)
}
}
Then, use this new status variable in the view: Text(customerViewModel.status.description).
i want to avoid fragment recreation by using BottomNavigationView
i read about that FragmentTransaction.replace is the problem and changing to add would help, but that didn't work for me..
maybe you can see here where i'm wrong
this is my Host Activity which hosting 3 fragments with BottomNavigationView
'''
class Host : AppCompatActivity() {
var fragment: Fragment? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_host)
//for hide the actionBar (contains Fragment name) on top of the screen
val actionBar = supportActionBar
actionBar?.hide()
val navController = Navigation.findNavController(this, R.id.nav_host_fragment)
val navView = findViewById<BottomNavigationView>(R.id.nav_view)
navView?.setupWithNavController(navController)
NavigationUI.setupWithNavController(navView, navController)
mAdapter = NfcAdapter.getDefaultAdapter(this)
fragmentStayAlive()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
for (fragment in supportFragmentManager.fragments) {
fragment.onActivityResult(requestCode, resultCode, data)
}
}
fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) }
private fun fragmentStayAlive(){
val fragment1: Fragment = LobbyFragment()
val fragment2: Fragment = GameSessionFragment()
val fragment3: Fragment = UserStatusFragment()
val fm: FragmentManager = supportFragmentManager
var active = fragment1
fm.beginTransaction().add(R.id.nav_host_fragment, fragment3, "UserStatusFragment").hide(
fragment3
).commit();
fm.beginTransaction().add(R.id.nav_host_fragment, fragment2, "GameSessionFragment").hide(
fragment2
).commit();
fm.beginTransaction().add(R.id.nav_host_fragment, fragment1, "LobbyFragment").commit();
val mOnNavigationItemSelectedListener: BottomNavigationView.OnNavigationItemSelectedListener =
object : BottomNavigationView.OnNavigationItemSelectedListener {
override fun onNavigationItemSelected(item: MenuItem): Boolean {
when (item.getItemId()) {
R.id.lobbyFragment -> {
fm.beginTransaction().hide(active).show(fragment1).commit()
active = fragment1
return true
}
R.id.gameSessionFragment -> {
fm.beginTransaction().hide(active).show(fragment2).commit()
active = fragment2
return true
}
R.id.userStatusFragment -> {
fm.beginTransaction().hide(active).show(fragment3).commit()
active = fragment3
return true
}
}
replaceFragment(active, null, true, true)
return false
}
}
}
fun replaceFragment(
fragment: Fragment,
#Nullable bundle: Bundle?,
popBackStack: Boolean,
findInStack: Boolean
) {
val fm = supportFragmentManager
val ft: FragmentTransaction = fm.beginTransaction()
val tag = fragment.javaClass.name
val parentFragment: Fragment?
parentFragment = if (findInStack && fm.findFragmentByTag(tag) != null) {
fm.findFragmentByTag(tag)
} else {
fragment
}
// if user passes the #bundle in not null, then can be added to the fragment
if (bundle != null) {
parentFragment!!.arguments = bundle
} else {
parentFragment!!.arguments = null
}
// this is for the very first fragment not to be added into the back stack.
if (popBackStack) {
fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
} else {
ft.addToBackStack(parentFragment.javaClass.name + "")
}
ft.add(R.id.nav_view, parentFragment, tag)
ft.commit()
fm.executePendingTransactions()
}
'''
thanks for all the helpers!
I have one activity with 2 recyclerViews, both recyclers are used with Firebase. One of the recyclers displays the results of a query, the other recycler has a listener that updates every time there is an update in Firebase. I added a searchView to filter the results from Firebase. The issue I'm having is when I'm trying to search the results from the Firebase query, when I start typing I see results but when I click the X to stop searching, the adapter does not reload the array and i don't see the list of items unless I reload the activity. I'm not sure what I'm missing here. Any help/suggestion is greatly appreciated. Here is my code:
Adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Filter
import android.widget.Filterable
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.eduardoz.ezmdapp.Model.Charges
import com.eduardoz.ezmdapp.R
class ChargesAdapter (private var charges: ArrayList<Charges>
, private var chargesAll: ArrayList<Charges>
, private val itemClick: (Charges) -> Unit)
: RecyclerView.Adapter<ChargesAdapter.ViewHolder>()
, Filterable {
inner class ViewHolder(itemView: View, val itemClick: (Charges) -> Unit) :
RecyclerView.ViewHolder(itemView) {
private val chargeCode = itemView.findViewById<TextView>(R.id.chargeCodeTxt)
private val chargeDescription = itemView.findViewById<TextView>(R.id.chargeDescriptionTxt)
fun bindCharges(charges: Charges) {
chargeCode?.text = charges.chargeCode
chargeDescription?.text = charges.chargeDescription
itemView.setOnClickListener { itemClick(charges) }
}
}
init {
this.charges = charges
chargesAll = java.util.ArrayList(charges)
}
override fun getItemCount(): Int {
return charges.count()
}
override fun onBindViewHolder(holder: ChargesAdapter.ViewHolder, position: Int) {
holder.bindCharges(charges[position])
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChargesAdapter.ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.charges_list, parent, false)
return ViewHolder(view, itemClick)
}
override fun getFilter(): Filter {
return searchFilter
}
private val searchFilter: Filter = object: Filter() {
override fun performFiltering(constraint: CharSequence?): FilterResults {
val filteredList: ArrayList<Charges> = ArrayList()
if (constraint!!.isEmpty()) {
filteredList.addAll(chargesAll)
} else {
for(item in chargesAll) {
if
(item.chargeDescription.toLowerCase().contains(constraint.toString().toLowerCase())) {
filteredList.add(item)
}
}
}
val searchResults = FilterResults()
searchResults.values = filteredList
return searchResults
}
override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
charges.clear()
charges.addAll(results!!.values as Collection<Charges>)
notifyDataSetChanged()
}
}
}
Activity
descriptionSearch.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
return false
}
override fun onQueryTextChange(newText: String): Boolean {
if (newText.isNotEmpty()) {
searchViewBar(newText)
} else {
if (newText.isEmpty()) { //I ADDED THIS TO RELOAD THE ADAPTER
charges.clear()
chargeList()
}
}
return false
}
})
private fun searchViewBar(newText: String) {
chargesListener = chargesCollectionRef
.whereGreaterThanOrEqualTo(CHARGE_DESCRIPTION, newText)
.whereLessThanOrEqualTo(CHARGE_DESCRIPTION, newText+"z")
.addSnapshotListener(this) { snapshot, exception ->
if (exception != null) {
println("error")
}
if (snapshot != null) {
charges.clear()
parseData(snapshot)
}
}
}
fun parseData(snapshot: QuerySnapshot) {
for (document in snapshot.documents) {
val data = document.data
val chargeCode = data!![CHARGE_CODE] as String
val chargeDescription = data[CHARGE_DESCRIPTION] as String
val chargeSpecialty = data[CHARGE_SPECIALTY] as String
val newChargeList = Charges(chargeCode, chargeDescription, chargeSpecialty)
charges.add(newChargeList)
}
chargesFromAdapter.notifyDataSetChanged()
}
My app layout consists of a tabbar, from each tab there is a navbar to which is attaches a UITableViewController.
When I didSelect a row, I push a view controller with becomes nicely embedded in the navigation bar.
I would like to find this latter view controller programmatically.
Here is how I push my visible view controller (streamlined version):
#objc
func tableView(_ tableView: UITableView, didSelectRowAtIndexPath indexPath: IndexPath) {
let vc = WebPageViewController()
navigationController?.pushViewController(vc, animated: true)
}
I did not managed to find a way. I am using this extension:
public extension UIWindow {
public var visibleViewController: UIViewController? {
return UIWindow.getVisibleViewControllerFrom(rootViewController)
}
public static func getVisibleViewControllerFrom(_ vc: UIViewController?) -> UIViewController? {
if let nc = vc as? UINavigationController {
return UIWindow.getVisibleViewControllerFrom(nc.visibleViewController)
} else if let tc = vc as? UITabBarController {
return UIWindow.getVisibleViewControllerFrom(tc.selectedViewController)
} else {
if let pvc = vc?.presentedViewController {
return UIWindow.getVisibleViewControllerFrom(pvc)
}
else if let pushed = vc?.childViewControllers.last {
return pushed
}
else {
return vc
}
}
}
}
everyone been trying to get data from one table using pointers, example User table and songs table, when you tap on a user it displays list of songs in the songs table specific to that users object id i tried using this method but i get an error query.wherekey("" equalTo: (PFUser.current()?.objectId!)!)
HERE IS MY CODE
var users = [""]
var userIDs = [""]
var isFollowing = ["" : true]
override func viewDidLoad() {
super.viewDidLoad()
//load user query
let query = PFUser.query ()
query?.findObjectsInBackground(block: { (objects, error) in
if error != nil {
print(error!)
} else if let users = objects {
for objects in users {
if let user = objects as? PFUser {
self.users.append(user.username!)
self.userIDs.append(user.objectId!)
let query = PFQuery(className: "Followers")
query.whereKey("Follower", equalTo: (PFUser.current()?.objectId!)!)
query.whereKey("Following", equalTo: user.objectId!)
query.findObjectsInBackground(block: { (objects, error) in
if let objects = objects {
if objects.count > 0 {
self.isFollowing[user.objectId!] = true
} else {
self.isFollowing[user.objectId!] = false
}
if self.isFollowing.count == self.users.count {
self.tableView.reloadData()
}
}
})
}
}
}
})
Thank You All
**HERE IS MY CODE FOR THE FIRST VIEW WHERE I AM ABLE TO GET ALL USERS AND SEGUE TO ANOTHER TABLE VIEW CONTROLLER**
//
// users.swift
//
//
// Created by Nawir on 1/17/17.
// Copyright © 2017 Nawir. All rights reserved.
//
import Foundation
import Parse
class artistsviewcontroller: UITableViewController {
var users = [""]
var userIDs = [""]
override func viewDidLoad() {
super.viewDidLoad()
//updating users array
let query = PFUser.query ()
query?.findObjectsInBackground(block: { (objects, error) in
if error != nil {
print(error!)
} else if let users = objects {
for objects in users {
if let user = objects as? PFUser {
self.users.append(user.username!)
self.userIDs.append(user.objectId!)
}
}
}
self.tableView.reloadData()
})
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let Usercell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! mTableViewCell
Usercell.textLabel!.text = users[indexPath.row]
return Usercell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// print("rowtapped: \(indexPath.row)")
let ItemlistTableViewController = self.storyboard?.instantiateViewController(withIdentifier: "itemviewscont") as! itemviewscont
self.navigationController?.pushViewController(ItemlistTableViewController, animated: true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
HERE IS MY SECOND TABLEVIEWCONTROLLER
// itemviews.swift
//
//
// Created by Nawir on 1/17/17.
// Copyright © 2017 Nawir. All rights reserved.
//
import Foundation
import Parse
class itemviewscont: UITableViewController {
var users = [String: String] ()
var itemname = [String] ()
override func viewDidLoad() {
super.viewDidLoad()
//query to get all user data from parse
let query = PFUser.query()
query?.findObjectsInBackground { (objects, error) in
if let users = objects {
self.users.removeAll()
for object in users {
if let user = object as? PFUser {
// self.users = [user.objectId!]
self.users[user.objectId!] = user.username!
}
}
}
//query to make connection
let getfollow = PFQuery(className: "Mkitems")
getfollow.whereKey("user", equalTo: PFUser.current()!)
getfollow.findObjectsInBackground(block: { (objects, error) in
if let followers = objects {
for object in followers {
if let follower = object as? PFObject {
let mainuser = follower["User"]
let query = PFQuery(className: "Music")
query.whereKey("fuser", equalTo: mainuser!)
query.findObjectsInBackground(block: { (objects,error) in
if let nitems = objects {
for object in nitems {
if let nitem = object as? PFObject {
self.musicname.append(nitem["name_title"] as! String)
self.tableView.reloadData()
}
}
}
})
}
}
}
})
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return itemname.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "mscell", for: indexPath)
cell.textLabel?.text = itemname[indexPath.row]
return cell
}
//dont pass this line its the end!
}
THANK YOU ALL
If you searching for current user object (assuming Follower is a pointer ) then use
query.whereKey("Follower", equalTo: PFUser.current()!)
and not :
query.whereKey("Follower", equalTo: (PFUser.current()?.objectId!)!)