I have two screens.
1.listing the food list
2.cart list
So in the foodlist i have cart button .So while clicking the cart button the name of the food should display in the cart list.
I have done in mvvm.
So in foodlistviewcontroller:-
cell.cartaddCell = {[weak self] in
if let i = self?.tableView.indexPath(for: $0) {
let cartmodel:CartModel = CartModel(withoffermodel:self!.offerViewModel.datafordisplay(atindex: indexPath))
let cartDataSource:ChartDataSourceModel = ChartDataSourceModel(array: nil)
let cartViewModel:ChartViewModel = ChartViewModel(withdatasource: cartDataSource)
cartViewModel.insertedArray = cartmodel
print(cartViewModel.insertedArray)
cartViewModel.add()
let cartViewController:ChartViewController = ChartViewController(nibName: "ChartViewController", bundle: nil, withViewModel: cartViewModel)
self?.navigationController?.pushViewController(cartViewController, animated: true)
// self?.present(cartViewController, animated: true, completion: nil)
// print(cartViewModel.insertedArray )
print(cartmodel.offerdetailAddName)
print(cartmodel)
print(i)
// self?.chartViewModel.delete(atIndex: i)
}
}
IN cartviewmodel:
func add() {
datasourceModel.dataListArray?.append(insertedArray!)
print(datasourceModel.dataListArray)
print(insertedArray?.offerdetailAddName)
}
So the name will display on the cartlist.
But when we directly click on the eventlist The name which added is not display in this screen.
So how to insert the row in this screen .
You'll need a reloadData on the UITableView.
Perhaps a delegate from the model to the UIViewController will be a good mechanism for this.
Related
I am able to delete the logged in users document when my app is first loaded. However, if I create a new entry, long press the new entry from the table view and select delete, I get a crash. I thought it had something to do with the document ID not being saved but I couldn't figure out why. If the same newly created entry is deleted after the app is closed and reopened then it will delete with no problem, but if I leave the app open and delete immediately after creating a new document, it will crash.
class BudgetViewController: UIViewController: {
var budgetData = [Transaction]()
func showAdd() {
let modalViewController = AddCategory()
modalViewController.addCategoryCompletion = { newCategories in
self.budgetData.append(newCategories)
self.tableView.reloadData()
}
modalViewController.modalPresentationStyle = .overFullScreen
modalViewController.modalTransitionStyle = .crossDissolve
modalViewController.selectionDelegate = self
present(modalViewController, animated: true, completion: nil)
}
#objc func handleLongPress(_ gestureRecognizer: UILongPressGestureRecognizer){
if gestureRecognizer.state == .began {
let touchPoint = gestureRecognizer.location(in: self.tableView)
if let indexPath = tableView.indexPathForRow(at: touchPoint) {
let cell = CategoryCell()
var data = budgetData[indexPath.row]
let modalViewController = EditCategory()
modalViewController.deleteCategory = { row in
self.deletedRow = row
self.deleteRow()
}
modalViewController.documentID = data.trailingSubText ?? ""
modalViewController.modalPresentationStyle = .overFullScreen
modalViewController.modalTransitionStyle = .crossDissolve
present(modalViewController, animated: true, completion: nil)
modalViewController.row = indexPath.row
print("longpressed\(indexPath.row)\(data.trailingSubText)")
}
}
}
override func viewDidLoad() {
loadNewData()
}
func loadNewData() {
guard let user = Auth.auth().currentUser?.uid else { return }
db.collection("users").document(user).collection("Category").getDocuments() {
snapshot, error in
if let error = error {
print("\(error.localizedDescription)")
} else {
for document in snapshot!.documents {
let data = document.data()
let title = data["title"] as? String ?? ""
let uid = data["uid"] as? String ?? ""
let documentID = document.documentID
// let timeStamp = data["timeStamp"] as? Date
let newSourse = Transaction(title: title, dateInfo: "0% out of spent", image: UIImage.gymIcon, amount: 12, annualPercentageRate: 12, trailingSubText: documentID, uid: uid)
self.budgetData.append(newSourse)
}
self.tableView.reloadData()
}
}
}
class AddCategory: UIViewController {
#objc func saveAction(){
guard let uid = Auth.auth().currentUser?.uid else { return }
let newCategory = Transaction(title: textField.text ?? "", dateInfo: "0% out of spent", image: UIImage.gymIcon, amount: 12, annualPercentageRate: 23, trailingSubText: "", uid: uid)
db.collection("users").document(uid).collection("Category").addDocument(data: newCategory.dictionary)
self.dismiss(animated: false, completion: {
self.addCategoryCompletion?(newCategory)
})
self.dismiss(animated: false, completion: nil)
print("selected")
}
}
}
class EditCategory: UIViewController {
func deleteAction(){
guard let user = Auth.auth().currentUser?.uid else { return }
print("document::\(self.documentID)")
// let budget = textField.text
db.collection("users").document(user).collection("Category").document(documentID).delete { (err) in
if let err = err {
print(err.localizedDescription)
}else{
self.dismiss(animated: false, completion: {
self.deleteCategory?(self.row)
})
print("deleted successfully")
}
}
}
}
The error is strongly suggesting that user is nil or empty at the time you run this code:
guard let user = Auth.auth().currentUser?.uid else { return }
db.collection("users").document(user).collection("Category").getDocuments()
This almost certainly means that a user was not signed in at the time. Your code needs to check currentUser for nil before trying to access its uid property. nil means that no user is currently signed in.
The user will not be signed in immediately at app launch. You should use an auth state listener to get a callback when the user object becomes available.
Maybe not the best approach, but it is working now. I called
self.budgetData.removeAll()
self.loadNewData()
self.tableView.reloadData()
inside of my callback
modalViewController.addCategoryCompletion = { newCategories in
self.budgetData.append(newCategories)
self.tableView.reloadData()
}`
Based on the presented code, when a new category is added to Firebase
let newCategory = Transaction(title: textField.text ?? ...)
db.collection("users").document(uid).collection("Category").addDocument(data: newCategory
you're not getting a valid documentId from Firebase first. So therefore that object exists in your dataSource with no documentId so when you try to remove it, there's a crash.
A couple of options
Option 1: Create a firebase reference first, which will provide a Firebase documentId that you can add to the object when writing. See the docs. Like this
let newCategoryRef = db.collection("Category").document()
let docId = newCategoryRef.documentId
...add docId to the category object, then add to dataSource
or
Option 2: Add an observer to the node (see Realtime Updates) so when a new document is written, the observers event will fire and present the newly added document, which will contain a valid documentId, and then craft an category object based on that data and add that object to your dataSource array. In this case, you don't need to add it to the dataSource array when writing as it will auto-add after it's written based on the observers .added event.
I have a ViewController with a UICollectionView and its elements are bound and cells created via:
self.viewModel.profileItems.bind(to: self.collectionView.rx.items){ (cv, row, item) ...
I also react to the user taps via:
self.collectionView.rx.modelSelected(ProfileItem.self).subscribe(onNext: { (item) in
if(/*special item*/) {
let xVC = self.storyboard?.instantiateViewController(identifier: "x") as! XViewController
xVC.item = item
self.navigationController?.pushViewController(xVC, animated: true)
} else {
// other generic view controller
}
}).disposed(by: bag)
The property in the xViewController for item is of Type ProfileItem?. How can changes to item in the XViewController be bound to the collectionView cell?
Thanks in advance
Your XViewController needs an Observable that emits a new item at the appropriate times... Then realize that this observable can affect what profileItems, or at least your view model, emits.
let xResult = self.collectionView.rx.modelSelected(ProfileItem.self)
.filter { /*special item*/ }
.flatMapFirst { [unowned self] (specialItem) -> Observable<Item> in
let xVC = self.storyboard?.instantiateViewController(identifier: "x") as! XViewController
xVC.item = item
self.navigationController?.pushViewController(xVC, animated: true)
return xVC.observableX // this needs to complete at the appropriate time.
}
self.collectionView.rx.modelSelected(ProfileItem.self)
.filter { /*not special item*/ }
.bind(onNext: { [unowned self] item in
// other generic view controller
}
.disposed(by: bag)
Now you need to feed xResult into your view model.
When I dismiss my customVC while inserting items inside collection view, the app crashes with index out of range error. It doesnt matter how much I remove all collectionView data sources, it still crashes. Heres what I do to insert items:
DispatchQueue.global(qos: .background).async { // [weak self] doesn't do much
for customs in Global.titles.prefix(8) {
autoreleasepool {
let data = self.getData(name: customs)
Global.customs.append(data)
DispatchQueue.main.async { [weak self] in
self?.insertItems()
}
}
}
}
func insertItems() { // this function helps me insert items after getting data and it works fine
let currentNumbers = collectionView.numberOfItems(inSection: 0)
let updatedNumber = Global.customs.count
let insertedNumber = updatedNumber - currentNumbers
if insertedNumber > 0 {
let array = Array(0...insertedNumber-1)
var indexPaths = [IndexPath]()
for item in array {
let indexPath = IndexPath(item: currentNumbers + item, section: 0)
indexPaths.append(indexPath)
}
collectionView.insertItems(at: indexPaths)
}
}
I tried to remove all items in customs array and reload collection view before dismissing but still getting error:
Global.customs.removeAll()
collectionView.reloadData()
dismiss(animated: true, completion: nil)
I suspect that since I load data using a background thread, the collection view inserts the items even when the view is unloaded (nil) but using DispatchQueue.main.async { [weak self] in self?.insertItems() } doesn't help either.
I am trying to transfer a JSON value to a label in another view controller, from pressing a button in my Main View Controller. My first view controller is called 'ViewController' and my second one is called 'AdvancedViewController. The code below shows how I get the JSON data, and it works fine, displays the JSON values in labels in my MainViewController, but when I go to send a JSON value to a label in my AdvancedViewController, I press the button, it loads the AdvancedViewController but the label value is not changed? I have assigned the label in my AdvancedViewController and I'm not sure why its not working. I am trying to transfer it to the value 'avc.Label' which is in my advanced view controller
The main label code shows how I get it to work in my MainViewController
Code below:
My Main ViewController:
guard let APIUrl = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=" + text + "&appid=e7b2054dc37b1f464d912c00dd309595&units=Metric") else { return }
//API KEY
URLSession.shared.dataTask(with: APIUrl) { data, response, error in
guard let data = data else { return }
let decoder = JSONDecoder()
//Decoder
do {
let weatherData = try decoder.decode(MyWeather.self, from: data)
if (self.MainLabel != nil)
{
if let gmain = (weatherData.weather?.first?.main) { //using .first because Weather is stored in an array
print(gmain)
DispatchQueue.main.async {
self.MainLabel.text! = String (gmain)
}
}
}
let avc = AdvancedViewController(nibName: "AdvancedViewController", bundle: nil)
if (avc.Label != nil)
{
if let mTest = (weatherData.weather?.first?.myDescription) { //using .first because Weather is stored in an array
DispatchQueue.main.async {
avc.Label.text! = String (mTest)
}
}
}
In AdvancedViewController create variable that store the value of mTest
class AdvancedViewController: ViewController {
var test: String!
override func viewDidLoad(){
super.viewDidLoad()
if let test = test {
myLabel.text = test
}
}
}
let vc = storyboard?.instantiateViewController(withIdentifier: "AdvancedViewController") as! AdvancedViewController
if let mTest = (weatherData.weather?.first?.myDescription) {
vc.test = mTest
}
DispatchQueue.main.async {
present(vc, animated: true, completion: nil)
}
You shouldn't try to manipulate another view controller's views. That violates the OO principle of encapsulation. (And it sometimes just plain doesn't work, as in your case.)
Salman told you what to do to fix it. Add a String property to the other view controller, and then in that view controller's viewWillAppear, install the string value into the desired label (or do whatever is appropriate with the information.)
I need your help! I don´t know how to change an array that is inserted on a TableCell from information I have in another ViewController. It’s a little bit messed up, but I’m gonna show you by my code.
Here I have a ViewController conformed by many switches that correspond to different categories of coupons, this is the code:
class FiltersViewController: UIViewController {
#IBOutlet weak var restaurantsSwitch: UISwitch!
#IBOutlet weak var sportsSwitch: UISwitch!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func returnHome(_ sender: Any) {
let vc = self.storyboard!.instantiateViewController(withIdentifier: "home") as! HomeViewController
self.present(vc, animated: false, completion: nil)
}
#IBAction func restaurants(_ sender: UISwitch) {
if restaurantsSwitch.isOn == true{
tuxtlaSwitch.isOn = false
sevillaSwitch.isOn = false
coapaSwitch.isOn = false
coyoacanSwitch.isOn = false
universidadSwitch.isOn = false
polancoSwitch.isOn = false
}
}
#IBAction func sports(_ sender: UISwitch) {
if sportsSwitch.isOn == true{
tuxtlaSwitch.isOn = false
sevillaSwitch.isOn = false
coapaSwitch.isOn = false
coyoacanSwitch.isOn = false
universidadSwitch.isOn = false
polancoSwitch.isOn = false
}
}
}
I’ve only show you two switches at the example with the purpose of not filling this with many code, but there are like 15 switches.
And in the other ViewController, which is connected to this one, the HomeViewController, contains coupons that comes from a JSON, and conforms an array of ten items displayed on a TableViewCell, the code:
class HomeViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var data : NSArray = []
var mainData : NSArray = []
var couponsImg : [UIImage] = []
var couponsTitle : [String] = []
var couponsDesc : [String] = []
var couponsCat : [String] = []
func getCoupons(){
let miURL = URL(string: RequestConstants.requestUrlBase)
let request = NSMutableURLRequest(url: miURL!)
request.httpMethod = "GET"
if let data = try? Data(contentsOf: miURL! as URL) {
do {
let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? NSDictionary
let parseJSON = json
let object = parseJSON?["object"] as! NSDictionary
let mainCoupon = object["mainCoupon"] as! NSArray
let coupons = object["coupons"] as! NSArray
self.mainData = mainCoupon
self.data = coupons
self.couponImg1 = (mainCoupon[0] as AnyObject).value(forKey: "urlImage") as! String
self.couponImg2 = (mainCoupon[1] as AnyObject).value(forKey: "urlImage") as! String
self.couponTitle1 = (mainCoupon[0] as AnyObject).value(forKey: "nameStore") as! String
self.couponTitle2 = (mainCoupon[1] as AnyObject).value(forKey: "nameStore") as! String
self.couponDesc1 = (mainCoupon[0] as AnyObject).value(forKey: "promoDescription") as! String
self.couponDesc2 = (mainCoupon[1] as AnyObject).value(forKey: "promoDescription") as! String
self.couponCat1 = (mainCoupon[0] as AnyObject).value(forKey: "category") as! String
self.couponCat2 = (mainCoupon[1] as AnyObject).value(forKey: "category") as! String
self.couponsImg = [couponImage1!, couponImage2!, couponImage3!, couponImage4!, couponImage5!, couponImage6!, couponImage7!, couponImage8!, couponImage9!, couponImage10!]
self.couponsTitle = [couponTitle1, couponTitle2, couponTitle3, couponTitle4, couponTitle5, couponTitle6, couponTitle7, couponTitle8, couponTitle9, couponTitle10]
self.couponsDesc = [couponDesc1, couponDesc2, couponDesc3, couponDesc4, couponDesc5, couponDesc6, couponDesc7, couponDesc8, couponDesc9, couponDesc10]
self.couponsCat = [couponCat1, couponCat2, couponCat3, couponCat4, couponCat5, couponCat6, couponCat7, couponCat8, couponCat9, couponCat10]
} catch {
let error = ErrorModel()
error.phrase = "PARSER_ERROR"
error.code = -1
error.desc = "Parser error in get Notifications action"
}
}
}
#IBAction func showFilters(_ sender: Any) {
let vc = self.storyboard!.instantiateViewController(withIdentifier: "filters") as! FiltersViewController
self.present(vc, animated: false, completion: nil)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! HomeTableViewCell
cell.couponImg.image = couponsImg[indexPath.row]
cell.couponTitle.text = couponsTitle[indexPath.row]
cell.couponDescription.text = couponsDesc[indexPath.row]
cell.couponCategory.text = couponsCat[indexPath.row]
return cell
}
(Again I’ve only showed you two coupons for the example). The thing is that I need to apply some filters to the coupons on the TableCell. The first time the view appear it shows the 10 coupons correctly, but when I go to the filters an put it some of them ON it doesn’t make a difference, the method I was trying to use was something like this, first have an instance of the FiltersViewController class:
var filters = FilterViewController()
if filters.isMovingToParentViewController == true {
if filters.restaurantsSwitch.isOn == false {
self.couponsImg.remove(at: 0)
self.couponsImg.remove(at: 1)
self.couponsImg.remove(at: 2)
}
if filters.sportsSwitch.isOn == false {
self.couponsImg.remove(at: 3)
self.couponsImg.remove(at: 4)
self.couponsImg.remove(at: 5)
}
}
In the example bellow I’m trying to say that if a have the restaurant switch off, I’m going to delete the corresponding coupons of the restaurant category, and the same with the sports switch. But first of all I don’t know where to include this logic, in which method? And also I don’t know if this instruction is correct for my purposes. Can somebody give me a hand please???
Your logic is not working because you're instantiating a new FilterViewController, different from the FilterViewController associated with you screen.
You can solve this using delegate.
First, create the delegate:
protocol FilterDelegate {
func updateTable() }
Then, In your FilterViewController add this line:
weak var delegate:FilterDelegate?
You HomeViewController have to conform with this delegate, so:
class HomeViewController: FilterDelegate ... {
func updateTable() {
/* GET THE DATA FILTERED HERE */
tableview.reloadData()
}
In your FilterViewController:
#IBAction func returnHome(_ sender: Any) {
let vc = self.storyboard!.instantiateViewController(withIdentifier: "home") as! HomeViewController
self.delegate = vc
self.present(vc, animated: false, completion: nil)
delegate?.updateTable()
}
I think that should work.
EDIT:
Another approach is to create a segue between these two vcs and pass the which filters are active using the "prepare" function . Then you can take this information in your HomeVC and load your table based on the filters in the viewDidLoad function.
1 - Create a object Filters:
class Filters {
var tuxtlaSwitchIsOn: Bool
var sevillaSwitchIsOn: Bool
...
init(tuxtlaSwitchIsOn: Bool, sevillaSwitchIsOn: Bool, ...) {
self.tuxtlaSwitchIsOn = tuxtlaSwitchIsOn
self.sevillaSwitchIsOn = sevillaSwitchIsOn
...
}
}
2 - Add a attribute Filters to your HomeVC
class HomeViewController : ... {
...
var filtersActive: Filters?
...
}
3 - In your FilterViewController instantiate a Filter object indicating which filters are on
4 - In your FilterViewController prepare funs pass the Filter object to HomeVC
5 - In your HomeVC, get the Filter object and filter your data based on it.
Sure here is what you need. So you have a set of array filled with data and you want to apply filter on them. First, you need to create another array for filter results. This is because when user removes the filter, you still want to show the full list. To simplify, say you only have an array Foo: [String]. So you need to create another array called FooFiltered: [String] to hold the search result. Your can leave it empty when the view controller is loaded.
Next, in your filter section, it's recommended to use array filter technology like this post, but it's okay if you want to do it in your way. So all you need to do is to get elements from Foo array that match certain criteria and copy them into FooFiltered array. Here let me show you an example of doing filter manually
func filter() {
FooFiltered = [String]() //Clean up every time before search
for str in Foo {
if str == "criteria" {
FooFiltered.append(str)
}
}
}
Now you have a list of filtered items. You need a flag to tell table view which set of array to display. Say you have a flag called showSearchResult that is set to false originally. When you do the filter, set it to true. So your cellForRow will look like
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if showSearchResult {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! Cell
cell.textField.text = FooFiltered[indexPath.row]
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! Cell
cell.textField.text = Foo[indexPath.row]
return cell
}
}
You also need to update this flag to all your table view delegate method, like numberOfRowsInSection, etc.
Finally, with these codes, your table view is configured to show full results or filtered results base on the flag and you are setting that flag in the filter() function. The last thing to do is to ask tableView to reload data when the filter is done. So modify your filter function like this and you should be all set.
func filter() {
FooFiltered = [String]() //Clean up every time before search
showSearchResul = true
for str in Foo {
if str == "criteria" {
FooFiltered.append(str)
}
}
self.tableView.reloadData()
}