How to do proper search as filter not working - swift4.2

I used:
var userArray:NSMutableArray = NSMutableArray()
view did load :
searchTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: UIControl.Event.editingChanged)
Then:
extension ChatListController : UITextFieldDelegate{
#objc func textFieldDidChange(_ textField: UITextField) {
userArray = userArray.filter { ($0 ["name"] as! String).range(of: textField.text!, options: [.diacriticInsensitive, .caseInsensitive]) != nil }
print("Filter userArray : ",userArray)
self.userTable.reloadData()
}
}
Throws error: # $0
error: Cannot subscript a value of incorrect or ambiguous type
var userArray:NSMutableArray = NSMutableArray()
extension ChatListController : UITextFieldDelegate{
#objc func textFieldDidChange(_ textField: UITextField) {
userArray = userArray.filter { ($0 ["name"] as! String).range(of: textField.text!, options: [.diacriticInsensitive, .caseInsensitive]) != nil }
print("Filter userArray : ",userArray)
self.userTable.reloadData()
}
}
Cannot subscript a value of incorrect or ambiguous type

#raj use this code
userArray = userArray.filter({($0["name"] as?
String)!.contains(searchString.lowercased())

Related

Custom Delegate Filter VC Swift5

I'm adding a custom delegate to my app and, for some reason, it is not working.
My app has a map where I show several markers of different company types. There is also a button that, once pressed, takes me to another viewController where the user can input some filters. The user then presses "Apply" which would pass the filtering data to the map viewController.
The issue here is that no data is being passed.
As reference I followed the guideline https://medium.com/#jamesrochabrun/implementing-delegates-in-swift-step-by-step-d3211cbac3ef which works perfectly fine.
Here is the full project code https://github.com/afernandes0001/Custom-Delegate
I use Firebase but code below just shows pieces related to the delegate.
mapViewController - you will notice that I added a print to the prepareForSegue. When first loading the app and clicking "Search" button it shows nav1 as nil (which is expected) but, if I click Search and Apply (in filterVC), that print is never done.
import UIKit
import MapKit
class MapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate, FilterVCDelegate {
#IBOutlet weak var map: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
map.register(MyAnnotationView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "clinicDetailsSegue" {
let clinicsDetailsViewController = segue.destination as! ClinicsDetailsViewController
clinicsDetailsViewController.id = self.note.mapId
} else if segue.identifier == "searchSegue" {
print("segue call")
let nav1 = segue.destination as? UINavigationController
print("nav1 \(nav1)")
if let nav = segue.destination as? UINavigationController, let filterVC = nav.topViewController as? FilterViewController {
filterVC.delegate = self
}
}
}
func chosenData(clinicNameFilter: String, stateFilter: String, cityFilter: String, esp1Filter: String, esp2Filter: String) {
print("Received data \(clinicNameFilter), \(stateFilter), \(cityFilter), \(esp1Filter), \(esp2Filter)")
}
}
FilterViewController
import UIKit
protocol FilterVCDelegate: class {
func chosenData(clinicNameFilter: String, stateFilter: String, cityFilter: String, esp1Filter: String, esp2Filter: String)
}
class FilterViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
weak var delegate: FilterVCDelegate?
var selectedName = ""
var statesJSON = [Estado]()
var cities = [Cidade]()
var state : Estate? // Selected State identifier
var city : City? // Selected City identifier
var selectedState = "" // Used to retrieve info from Firebase
var selectedCity = "" // Used to retrieve info from Firebase
var specialtiesJSON = [Specialty]()
var specialties2 = [Specialty2]()
var specialty1 : Specialty? // Selected Specialty1 identifier
var specialty2 : Specialty2? // Selected Specialty2 identifier
var selectedSpecialty1 = ""
var selectedSpecialty2 = ""
#IBOutlet weak var clinicName: UITextField!
#IBOutlet weak var statePicker: UIPickerView!
#IBOutlet weak var esp1Picker: UIPickerView!
#IBOutlet weak var esp2Picker: UIPickerView!
override func viewDidLoad() {
readJsonStates()
readJsonSpecialties()
super.viewDidLoad()
clinicName.text = ""
}
#IBAction func applyFilter(_ sender: Any) {
if clinicName.text == nil {
clinicName.text = ""
}
if selectedState != "" {
if selectedCity != "" {
if selectedSpecialty1 != ""{
if selectedSpecialty2 != "" {
delegate?.chosenData(clinicNameFilter: clinicName.text!, stateFilter: selectedState, cityFilter: selectedCity, esp1Filter: selectedSpecialty1, esp2Filter: selectedSpecialty2)
let viewControllers: [UIViewController] = self.navigationController!.viewControllers as [UIViewController]
self.navigationController?.popToViewController(viewControllers[viewControllers.count - 2], animated: true)
} else {
print("Fill in all filter data")
}
} else {
print("Fill in all filter data")
}
} else {
print("Fill in all filter data")
}
} else {
print("Fill in all filter data")
}
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
esp1Picker.reloadComponent(0)
esp2Picker.reloadComponent(0)
statePicker.reloadAllComponents()
if pickerView == statePicker {
if component == 0 {
self.state = self.statesJSON[row]
self.coties = self.statesJSON[row].cities
statePicker.reloadComponent(1)
statePicker.selectRow(0, inComponent: 1, animated: true)
} else {
self.city = self.cities[row]
statePicker.reloadAllComponents()
}
} else if pickerView == esp1Picker {
self.specialty1 = self.specialtiesJSON[row]
self.specialties2 = self.specialtiesJSON[row].specialty2
esp1Picker.reloadComponent(0)
esp2Picker.reloadComponent(0)
esp2Picker.selectRow(0, inComponent: 0, animated: true)
} else if pickerView == esp2Picker {
self.specialty2 = self.specialties2[row]
esp1Picker.reloadComponent(0)
esp2Picker.reloadComponent(0)
}
let indexSelectedState = statePicker.selectedRow(inComponent: 0)
let indexSelectedCity = statePicker.selectedRow(inComponent: 1)
let indexSelectedEsp1 = esp1Picker.selectedRow(inComponent: 0)
let indexSelectedEsp2 = esp2Picker.selectedRow(inComponent: 0)
if indexSelectedState >= 0 {
if indexSelectedCity >= 0 {
selectedState = estadosJSON[indexSelectedState].name
selectedCity = cidades[indexSelectedCity].name
}
}
if indexSelectedEsp1 >= 0 {
if indexSelectedEsp2 >= 0 {
selectedSpecialty1 = specialtiesJSON[indexSelectedEsp1].name
selectedSpecialty2 = specialtiesJSON[indexSelectedEsp1].specialty2[indexSelectedEsp2].name
}
}
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
if pickerView == statePicker {
return 2
} else if pickerView == esp1Picker {
return 1
} else if pickerView == esp2Picker {
return 1
}
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if pickerView == statePicker {
if component == 0 {
return statesJSON.count
} else {
return cities.count
}
} else if pickerView == esp1Picker {
return self.specialtiesJSON.count
} else if pickerView == esp2Picker {
return specialties2.count
}
return 1
}
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
var rowTitle = ""
let pickerLabel = UILabel()
pickerLabel.textColor = UIColor.black
if pickerView == statePicker {
if component == 0 {
rowTitle = statesJSON[row].name
} else {
rowTitle = cities[row].name
}
} else if pickerView == esp1Picker {
rowTitle = specialtiesJSON[row].name
} else if pickerView == esp2Picker {
rowTitle = specialties2[row].name
}
pickerLabel.text = rowTitle
pickerLabel.font = UIFont(name: fontName, size: 16.0)
pickerLabel.textAlignment = .center
return pickerLabel
}
func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat {
if pickerView == statePicker {
if component == 0 {
return 50
} else {
return 300
}
}
return 300
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
view.endEditing(true)
}
func readJsonStates() {
let url = Bundle.main.url(forResource: "StatesAndCities", withExtension: "json")!
do {
let data = try Data(contentsOf: url)
let jsonResult = try JSONDecoder().decode(RootState.self, from: data)
//handles the array of countries on your json file.
self.statesJSON = jsonResult.state
self.cities = self.statesJSON.first!.cities
} catch {
}
}
func readJsonSpecialties() {
let url = Bundle.main.url(forResource: "Specialties", withExtension: "json")!
do {
let data = try Data(contentsOf: url)
let jsonResult = try JSONDecoder().decode(RootEsp.self, from: data)
//handles the array of specialties on your json file.
self.specialtiesJSON = jsonResult.specialty
self.specialties2 = self.specialtiesJSON.first!.specialty2
} catch {
}
}
}
Any idea why, when I click ApplyFilter, delegate is not updated in the MapViewController?
Thanks
I found the error in my project.
The issue was with my Navigation Controller.
When I posted the error above, my Storyboard looked like the below
To make it work, I added the Navigation Controller to the Filter View Controller as below
That did the work and protocol is working as expected.

How do I filter data from database arrays using Search bar

Im using the search bar to filter data from the database and will populate the tableView. Im getting an error on the isSearching part.
Value of type 'DataSnapshot' has no member 'contains'
Heres the code.
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar.text == nil || searchBar.text == "" {
isSearching = false
view.endEditing(true)
tableView.reloadData()
} else {
isSearching = true
filteredColorRequests = colors.filter{$0.contains(searchBar.text!)}
tableView.reloadData()
}
}
How
You certainly want to search for a specific String property for example a name.
And rather than getting the search string form the bar use the searchText parameter which is already non-optional.
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchText.isEmpty {
isSearching = false
view.endEditing(true)
} else {
isSearching = true
filteredColorRequests = colors.filter{(($0.value as! [String:Any])["name"] as! String).contains(searchText)}
}
tableView.reloadData()
}

Found nil while unwrapping an optional value - Swift 2

I am getting this error which I cannot explain:
#IBOutlet weak var licencePlateLabel: UILabel!
var editItem: CarValues? {
didSet {
// Update the view.
self.configureView()
}
}
func configureView() {
// Update the user interface for the detail item.
if let editedCar = self.editItem {
if let licencePlate = self.licencePlateLabel {
licencePlate.text? = editedCar.licencePlate!//this gives me nil
}
else {
print("value was nil")
}
print(editedCar.licencePlate!)//this is giving me the correct value
}
if I replace the
if let licencePlate = self.licencePlateLabel {
licencePlate.text! = editedCar.licencePlate!
}//this throws an error "found nil......"
even if I do this I m still getting the "found nil..."
func configureView() {
licencePlateLabel.text = "test"
[...]
}
BUT if I put the above on viewDidLoad then it works fine
override func viewDidLoad() {
licencePlateLabel.text = "test"
[...]
}
What is going on in this code?
EDIT
I am passing the value of the editItem from the detailView to the EditView like this:
#IBAction func editButtonTapped(sender: AnyObject) {
let storyBoard = UIStoryboard(name: "Main", bundle:nil)
let editScreen = storyBoard.instantiateViewControllerWithIdentifier("ID_EditViewController")
self.navigationController?.pushViewController(editScreen, animated: true)
let controller = EditViewController()
controller.editItem = detailItem
controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem()
controller.navigationItem.leftItemsSupplementBackButton = true
}
You don't unwrap properties to set them, only to read from them. So your code can simply be:
if let licencePlate = self.licencePlateLabel {
licencePlate.text = editedCar.licencePlate
}
Note that because licencePlate.text is an optional value anyway, there is also no need to unwrap editedCar.licencePlate. It's ok to use its value whether it is nil or contains a String.

Parse.com - Download Objects From Database - Show Progress With ProgressBlock

I have Parse class called Product that has 238 rows. Note that this class is not the Parse.com implementation of Product, it is a custom class implemented by myself, as I didn't require all the columns Parse adds to their Product class.
The Product class has a Pointer column (basically a foreign key in SQL tables), called ShopId, because each product belongs to a specific Shop (I have a Parse class called Shop with an ObjectId column used in the Product Pointer.
My Product class also has a File column called imageFile that holds the image of the product.
I want to download all Products from a specific shop, unpackage their image file and put it in my Swift Product class which consists of the PFObject of the Parse Product, and a UIImageView and a UIImage. Here is my Product Class in Swift:
class Product {
private var object: PFObject
private var imageView: MMImageView!
private var image: UIImage
init(object: PFObject, image: UIImage) {
self.object = object
self.image = image
}
func getName() -> String {
if let name = object["name"] as? String {
return name
} else {
return "default"
}
}
func setImageView(size: CGFloat, target: DressingRoomViewController) {
self.imageView = MMImageView(frame:CGRectMake(0, 0, size, size))
imageView.contentMode = UIViewContentMode.ScaleAspectFit
imageView.image = self.image
imageView.setName(object["category"] as! String)
imageView.backgroundColor = UIColor.clearColor()
imageView.userInteractionEnabled = true
let tapGestureRecognizer =
UITapGestureRecognizer(target: target, action: "imageTapped:")
tapGestureRecognizer.numberOfTapsRequired = 1
imageView.addGestureRecognizer(tapGestureRecognizer)
}
func getImageView() -> MMImageView {
return self.imageView
}
}
I am currently downloading all the products just fine, and getting their image file and creating my Swift Products with their images. However my UIProgressView logic is slightly off. I have the UIProgressView running for every product, every time I unpackage the product image. I need to shift the Parse.com ProgressBlock out of the getProduct swift function and into the loadProducts #IBAction. When I try it, it causes a lot of errors before compilation. How do I shift the ProgressBlock up to the loadProducts #IBAction? Here is my current code:
//
// ChooseShopViewController.swift
// MirrorMirror
//
// Created by Ben on 12/09/15.
// Copyright (c) 2015 Amber. All rights reserved.
//
import UIKit
import Parse
class ChooseShopViewController: UIViewController {
var progressView: UIProgressView?
private var allProducts: [Product] = []
private var categories: [ProductCategory] = []
#IBAction func loadProducts(sender: AnyObject) {
let shopQuery = PFQuery(className:"Shop")
shopQuery.getObjectInBackgroundWithId("QjSbyC6k5C") {
(glamour: PFObject?, error: NSError?) -> Void in
if error == nil && glamour != nil {
let query = PFQuery(className:"Product")
query.whereKey("shopId", equalTo: glamour!)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
self.getAllProductsAndCategories(objects, error: error)
}
} else {
print(error)
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Create Progress View Control
progressView = UIProgressView( progressViewStyle:
UIProgressViewStyle.Default)
progressView?.center = self.view.center
view.addSubview(progressView!)
}
override func prepareForSegue( segue: UIStoryboardSegue,
sender: AnyObject?) {
if (segue.identifier == "dressingRoom") {
ShopDisplay.sharedInstance.setAllProducts(self.allProducts)
ShopDisplay.sharedInstance.setAllProductCategories(self.categories)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func getAllProductsAndCategories(objects: [AnyObject]?, error: NSError?) {
if error == nil {
if let objects = objects as? [PFObject] {
for product in objects {
self.getCategory(product)
self.getProduct(product)
}
}
} else {
print("Error: \(error!) \(error!.userInfo)")
}
}
func getCategory(product: PFObject) {
if let category = product["category"] as? String {
var alreadyThere: Bool = false
for item in self.categories {
if category == item.rawValue {
alreadyThere = true
break
}
}
if alreadyThere == false {
self.categories.append(ProductCategory(rawValue: category)!)
}
}
}
func getProduct(product: PFObject) {
if let productImage = product["imageFile"] as? PFFile {
productImage.getDataInBackgroundWithBlock ({
(imageData: NSData?, error: NSError?) -> Void in
if let imageData = imageData {
let image = UIImage(data:imageData)
self.allProducts.append(
Product(object: product, image: image!))
}
if let downloadError = error {
print(downloadError.localizedDescription)
}
}, progressBlock: {
(percentDone: Int32) -> Void in
self.progressView?.progress = Float(percentDone)
if (percentDone == 100) {
//self.performSegueWithIdentifier("dressingRoom", sender: UIColor.greenColor())
}
})
}
}
}
I decided to not use the progressBlock, and instead to update my UIProgressView manually with a calculation. So here is the code. It's a little rusty. I could refactor now and maybe implement a calculated variable to make it cleaner. If my solution is a bad practice then I'm appreciative if that gets pointed out, and a better solution suggested (It doesn't seem good for performance to check the UIProgressView.progress value every iteration to perform the completion task of performing the segue).
import UIKit
import Parse
class ChooseShopViewController: UIViewController {
var progressView: UIProgressView?
private var allProducts: [Product] = []
private var categories: [ProductCategory] = []
static var numberOfProducts: Float = 0
#IBAction func loadProducts(sender: AnyObject) {
let shopQuery = PFQuery(className:"Shop")
shopQuery.getObjectInBackgroundWithId("QjSbyC6k5C") {
(glamour: PFObject?, error: NSError?) -> Void in
if error == nil && glamour != nil {
let query = PFQuery(className:"Product")
query.whereKey("shopId", equalTo: glamour!)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
ChooseShopViewController.numberOfProducts =
Float((objects?.count)!)
print(ChooseShopViewController.numberOfProducts)
self.getAllProductsAndCategories(objects, error: error)
}
} else {
print(error)
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Create Progress View Control
progressView = UIProgressView( progressViewStyle:
UIProgressViewStyle.Default)
progressView?.center = self.view.center
progressView?.progress = 0.00
view.addSubview(progressView!)
}
override func prepareForSegue( segue: UIStoryboardSegue,
sender: AnyObject?) {
if (segue.identifier == "dressingRoom") {
ShopDisplay.sharedInstance.setAllProducts(self.allProducts)
ShopDisplay.sharedInstance.setAllProductCategories(self.categories)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func getAllProductsAndCategories(objects: [AnyObject]?, error: NSError?) {
if error == nil {
if let objects = objects as? [PFObject] {
for product in objects {
self.getCategory(product)
self.getProduct(product)
}
}
} else {
print("Error: \(error!) \(error!.userInfo)")
}
}
func getCategory(product: PFObject) {
if let category = product["category"] as? String {
var alreadyThere: Bool = false
for item in self.categories {
if category == item.rawValue {
alreadyThere = true
break
}
}
if alreadyThere == false {
self.categories.append(ProductCategory(rawValue: category)!)
}
}
}
func getProduct(product: PFObject) {
if let productImage = product["imageFile"] as? PFFile {
productImage.getDataInBackgroundWithBlock ({
(imageData: NSData?, error: NSError?) -> Void in
if let imageData = imageData {
let image = UIImage(data:imageData)
self.allProducts.append(
Product(object: product, image: image!))
self.progressView?.progress += (100.00 /
ChooseShopViewController.numberOfProducts) / 100.00
print(self.progressView?.progress)
if self.progressView?.progress == 1 {
self.performSegueWithIdentifier("dressingRoom",
sender: UIColor.greenColor())
}
}
if let downloadError = error {
print(downloadError.localizedDescription)
}
})
}
}
}
I found this on the Parse website. It may be useful as it has a block that shows the percentage done that updates regularly during the download!
let str = "Working at Parse is great!"
let data = str.dataUsingEncoding(NSUTF8StringEncoding)
let file = PFFile(name:"resume.txt", data:data)
file.saveInBackgroundWithBlock({
(succeeded: Bool, error: NSError?) -> Void in
// Handle success or failure here ...
}, progressBlock: {(percentDone: Int32) -> Void in
// Update your progress spinner here. percentDone will be between 0 and 100.
})
Did you find a better solution? besides this? I am trying to do something similar.

unexpectedly found nil while unwrapping an Optional value(optional binding)

import UIKit
import Parse
class HomePageViewController: UIViewController, UITableViewDelegate {
#IBOutlet weak var homPageTableView: UITableView!
var imageFiles = [PFFile]()
var imageText = [String]()
override func viewDidLoad() {
super.viewDidLoad()
// DO any additional setup after loading the view
var query = PFQuery(className: "Posts")
query.orderByAscending("createdAt")
query.findObjectsInBackgroundWithBlock {
(posts : [AnyObject]?, error : NSError?) -> Void in
if error == nil {
//success fetxhing objects
println(posts?.count)
for post in posts! {
self.imageFiles.append(post["imageFile"] as! PFFile) ---------error here
self.imageText.append(post["imageText"] as! String)
}
println(self.imageFiles.count)
}else{ println(error)
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
As a title, It is keep saying "unexpectedly found nil while unwrapping an Optional value" at the line I draw.
There are a lot of questions about this, it is hard to read code for me, And even if I undertood, mine didn't go right.
where should I use optional binding?
And can u explain it with really easy example what is optional binding?
Thank you
Your post dictionary does not contain an imageFile key. When you access a dictionary using post["imageFile"] the result is the value (if the key exists) and nil (if the key does not exist). You can distinguish between these cases using
if let imageFile = post["imageFile"],
let imageText = post["imageText"] {
self.imageFiles.append(imageFile as! PFFile)
self.imageText.append(imageText as! String)
} else {
print("imageFile and/or imageText missing from \(post)")
}
You need to unwrap your posts before looping through them because posts: [AnyObject]? is optional.
if error == nil {
if let postData = posts{
//success fetxhing objects
println(postData?.count)
for post in postData! {
self.imageFiles.append(post["imageFile"] as! PFFile)
self.imageText.append(post["imageText"] as! String)
}
println(self.imageFiles.count)
}
}else{
println(error)
}

Resources