Search for a string in an array of objects - ios

I'm doing a product search engine in my application.
I have this code:
func searchStringInProductArray(searchString: String) {
let documentsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let path = documentsDir.appendingPathComponent((AppGlobalManager.sharedManager.loggedUser?.selectedLanguage)! + "/json/products.json")
do {
let data = try Data(contentsOf: path)
let decoder = JSONDecoder()
var productsTmpObjectArray = [Products]()
productsTmpObjectArray = try decoder.decode([Products].self, from: data)
productsTmpObjectArray = productsTmpObjectArray.filter({($0.code?.contains(searchString))!})
productsTmpObjectArray = productsTmpObjectArray.filter({($0.name?.contains(searchString))!})
productsTmpObjectArray = productsTmpObjectArray.filter({($0.langVersions.name?.contains(searchString))!})
productsTmpObjectArray = productsTmpObjectArray.filter({($0.langVersions?.description?.contains(searchString))!})
productsObjectArray = productsTmpObjectArray
collectionView1.reloadData()
} catch {
NotificationCenter.default.post(name: Notification.Name("NotificationAlertMessage"), object: nil, userInfo: ["object": errorMessage(title: "Blad".localized(), description: "problemWithParseData".localized(), buttonTitle: "Ok".localized())])
}
}
and object:
struct ChangeTime : Codable {
let year : Int?
let month : Int?
let dayOfMonth : Int?
let hourOfDay : Int?
let minute : Int?
let second : Int?
}
struct Lang : Codable {
let id : Int?
let code : String?
let name : String?
}
struct LangVersions : Codable {
let id : Int?
let parentId : Int?
let name : String?
let lang : Lang?
let description : String?
}
struct Products : Codable {
let id : Int?
let code : String?
let name : String?
let inPieces : Bool?
let prepDeepFryer : String?
let langVersions : [LangVersions]?
let changeTime : ChangeTime?
}
The function is called after entering text in TextField:
#IBAction func searchProductTextFieldChanged(_ sender: Any) {
searchStringInProductArray(searchString: searchTextField.text!)
}
I have the following errors:
productsTmpObjectArray = productsTmpObjectArray.filter({($0.langVersions.name?.contains(searchString))!}) - Value of type '[LangVersions]?' has no member 'name'
productsTmpObjectArray = productsTmpObjectArray.filter({($0.langVersions?.description?.contains(searchString))!}) - Cannot use optional chaining on non-optional value of type 'String'
I would like my search engine to search for all fields at once:
$0.code?.contains(searchString)
$0.name?.contains(searchString)
$0.langVersions.name?.contains(searchString)
$0.langVersions?.description?contains(searchString)

You haven't noticed that 'Products' object contains array of 'LangVersions' object. edited version of your method:
func searchStringInProductArray(searchString: String) {
let documentsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let path = URL(string: "")!
do {
let data = try Data(contentsOf: path)
let decoder = JSONDecoder()
var productsTmpObjectArray = [Products]()
productsTmpObjectArray = try decoder.decode([Products].self, from: data)
productsObjectArray = productsTmpObjectArray.filter({return ($0.code?.lowercased().contains(searchString.lowercased()))! || ($0.name?.lowercased().contains(searchString.lowercased()))! || (($0.langVersions?.filter({($0.name?.lowercased().contains(searchString.lowercased()))!}))?.count)! > 0 || (($0.langVersions?.filter({($0.description?.lowercased().contains(searchString.lowercased()))!}))?.count)! > 0 })
collectionView1.reloadData()
} catch {
NotificationCenter.default.post(name: Notification.Name("NotificationAlertMessage"), object: nil, userInfo: ["object": errorMessage(title: "Blad".localized(), description: "problemWithParseData".localized(), buttonTitle: "Ok".localized())])
}
}

You can try this to search for all values
let searchString = "searStr".lowercased()
productsTmpObjectArray = productsTmpObjectArray.filter { ($0.name?.lowercased().contains(searchString))! || ($0.code?.lowercased().contains(searchString))! || $0.langVersions?.filter {($0.name?.lowercased().contains(searchString))!}.count != 0 || $0.langVersions?.filter {($0.description?.lowercased().contains(searchString))!}.count != 0 }

Related

Unexpectedly found nil while unwrapping... MessageKit

I have a struct set up for Messages and each time I got to load messages in app, I receive an unexpectedly found nil error on this line of code -->
var chatPartnerId: String {
return isFromCurrentUser ? toID! : fromID! // where I get the error
}
I can't figure out what Im doing wrong here at all.
Here's the class setup:
struct Message: MessageType {
let id: String?
var messageId: String {
return id ?? UUID().uuidString
}
var content: String?
var toID: String?
var fromID: String?
var isFromCurrentUser = Bool()
var chatPartnerId: String {
return isFromCurrentUser ? toID! : fromID!
}
let sentDate: Date
let sender: SenderType
var image: UIImage?
var downloadURL: URL?
var kind: MessageKind {
if let image = image {
let mediaItem = ImageMediaItem(image: image)
return .photo(mediaItem)
} else {
return .text(content ?? "")
}
}
init(user: User, content: String, fromID: String, toID: String) {
sender = Sender(senderId: user.uid!, displayName: user.name!)
self.content = content
self.fromID = fromID
self.toID = toID
sentDate = Date()
id = nil
}
init(user: User, image: UIImage) {
sender = Sender(senderId: user.uid!, displayName: user.name!)
self.image = image
content = ""
fromID = ""
toID = ""
sentDate = Date()
id = nil
}
init?(document: QueryDocumentSnapshot) {
let data = document.data()
guard
let sentDate = data["created"] as? Timestamp,
let senderId = data["senderId"] as? String,
let fromID = data["fromID"] as? String,
let toID = data["toID"] as? String,
let senderName = data["senderName"] as? String
else {
return nil
}
id = document.documentID
self.sentDate = sentDate.dateValue()
sender = Sender(senderId: senderId, displayName: senderName)
self.isFromCurrentUser = fromID == Auth.auth().currentUser?.uid
if let content = data["content"] as? String {
self.content = content
downloadURL = nil
} else if let urlString = data["url"] as? String, let url = URL(string: urlString) {
downloadURL = url
content = ""
} else {
return nil
}
}
}
// MARK: - DatabaseRepresentation
extension Message: DatabaseRepresentation {
var representation: [String: Any] {
var rep: [String: Any] = [
"created": sentDate,
"senderId": sender.senderId,
"fromID": fromID,
"toID": toID,
"senderName": sender.displayName
]
if let url = downloadURL {
rep["url"] = url.absoluteString
} else {
rep["content"] = content
}
return rep
}
}
// MARK: - Comparable
extension Message: Comparable {
static func == (lhs: Message, rhs: Message) -> Bool {
return lhs.id == rhs.id
}
static func < (lhs: Message, rhs: Message) -> Bool {
return lhs.sentDate < rhs.sentDate
}
}
Both toID and fromID are optionals and may be nil. Avoid force unwrapping the optional (and actually avoid force unwrapping anything else, with very rare exceptions), like you do in the problematic statement.
Instead, you can:
Don't be afraid to return an optional:
var chatPartnerId: String? { // <-- returns optional
return isFromCurrentUser ? toID : fromID
}
In many cases it's much better to deal with the nil as a condition that helps you understand the state of the app. For example nil may mean you should skip the processing of such message.
You can return a default bogus ID, or an empty string:
var chatPartnerId: String {
guard let id = isFromCurrentUser ? toID : fromID else {
return "" // <-- returns bogus ID
}
return id
}
You can change the property to be required:
var toID: String // <-- not optional
var fromID: String // <-- not optional
Looking at all of your inits I see none of them allows these paramters to be nil. So you don't need to make them optional.

How to get a UIImage from a String with JSON data

I am trying to load an icon image form the API OpenWeatherMap, and display it in an ImageView. I am trying to load it into the imageView 'iconImage'. I have successfully loaded the JSON data for the location and humidity, as they are Strings, but the Icon data is also a String and I cannot get it to display as a UIImage.
Code below:
My JSON Structs below:
struct Coordinate : Decodable {
let lat, lon : Double?
}
struct Weather : Decodable {
var id : Int?
var main, myDescription, icon : String?
enum CodingKeys : String, CodingKey {
case id = "id"
case main = "main"
case icon = "icon"
case myDescription = "description"
}
}
struct Sys : Decodable {
let type, id : Int?
let sunrise, sunset : Date?
let message : Double?
let country : String?
}
struct Main : Decodable {
let temp, tempMin, tempMax : Double?
let pressure, humidity : Int?
}
struct Wind : Decodable {
let speed : Double?
let deg : Int?
}
struct MyWeather : Decodable {
let coord : Coordinate?
let cod, visibility, id : Int?
let name : String?
let base : String?
let weather : [Weather]?
let sys : Sys?
let main : Main?
let wind : Wind?
let dt : Date?
}`enter code here`
View controller below:
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 {
if (self.iconImage != nil)
{
if let gicon = weatherData.weather?.first?.icon {
DispatchQueue.main.async {
self.iconImage.image! = gicon
}
}
}
if (self.LocationLabel != nil)
{
if let gmain = weatherData.name {
print(gmain)
DispatchQueue.main.async {
self.LocationLabel.text! = "Current Weather in: " + String (gmain)
}
}
}
if (self.HumidityLabel != nil)
{
if let ghumidity = weatherData.main?.humidity
{
print(ghumidity, "THIS IS HUMIDITY")
DispatchQueue.main.async {
self.HumidityLabel.text! = String (ghumidity)
}
}
}
Use Kingfisher
It creates an extension in ImageView. You will use self.imageview.kf.setImage(with: "url")
Icon is the id of the image , you need to append it to this url and load it
http://openweathermap.org/img/w/10d.png // here ------ id = 10d
suppose you'll use SDWebImage , then do this
let urlStr = "http://openweathermap.org/img/w/\(gicon).png"
self.iconImage.sd_setImage(with: URL(string:urlStr), placeholderImage: UIImage(named: "placeholder.png"))
See here in Docs

Parse local json file to object

I have this code:
struct ProductObject : Codable {
let palletHeight : Double?
let layerPallet : Int?
let prepCombisteamer : String?
let id : Int?
let avikoWorlds : [String]?
let avikoSegments : [String]?
let sunFlower : Bool?
let inPieces : Bool?
let noBox : Int?
let prepFryingPan : String?
let packageContents : Double?
let carbohydrates : Int?
let fat : Double?
let eanBox : Int?
let weightYieldPercent : Int?
let kcal : Int?
let markedAsFavourite1 : Bool?
let avikoPodSegmentyRynku : [String]?
let prepPot : String?
let prepMicrowave : String?
let name : String?
let code : Int?
let prepDeepFryer : String?
let avikoConcepts : [String]?
let boxLayer : Int?
let avikoSegmentyRynku : [String]?
let active : Bool?
let shelfLifeTimeFrame : String?
let markedAsFavourite2 : Bool?
let palletWeight : Double?
let changeTime : ChangeTime?
let kj : Int?
let langVersions : [LangVersions]?
let proteins : Double?
let markedAsFavourite3 : Bool?
let containsGluten : Bool?
let regions : [Int]?
let eanFoil : Int?
let shelfLife : Int?
let contentPerBox : Int?
let prepOven : String?
}
func downloadImagesFromJsonProductFile(fileName: URL){
let filePath = fileName.path
let fileManager = FileManager.default
if fileManager.fileExists(atPath: filePath) {
do {
let data = try Data(contentsOf: fileName)
let jsonData = try JSONDecoder().decode(ProductObject.self, from: data)
} catch let error {
self.errorLoginMessage(txt: "MainView - Error 109: Problem with parse file \(error)", title: "Blad".localized())
}
}
}
downloadImagesFromJsonProductFile(fileName: documentsDir.appendingPathComponent((AppGlobalManager.sharedManager.loggedUser?.selectedLanguage)! + "/json/products.json"))
My local json file: https://files.fm/u/73n845ty
When I run function: downloadImagesFromJsonProductFile I have error
(when app was started): Problem with parse file typeMismatch(Swift.Dictionary, Swift.DecodingError.Context(coding-path:[], debugDescription: "Expected the decode Dictionary but found an array instead.", underlyingError: will).
How to fix it?
As I review your JSON here http://json.parser.online.fr/, there is array as root object, therefore you should do
let json = try JSONDecoder().decode([ProductObject].self, from: data)
UPDATE
Please create JSON struct Codable from here json4swift, please resolve type mismatch errors first otherwise your JSON could not be parsed and you will get type Mismatch errors.
You can get product code by following,
let jsonData = try JSONDecoder().decode([ProductObject].self, from: data)
for detail in jsonData {
print(detail.code ?? "")
}

Implement Google Details in my classes to retrive information about nearby places

I created these two classes to download nearby places and get the details about them (like name, distance, type etc.)
import Foundation
import UIKit
import CoreLocation
import Alamofire
class NearbyPlaces {
static func getCategories() -> [QCategoryy] {
let list:[QCategoryy] = [QCategoryy(name: "bar", image: UIImage(named: "bar_button.png")!), QCategoryy(name :"night_club", image: UIImage(named: "nightclub_button.png")!), QCategoryy(name: "restaurant", image: UIImage(named: "restaurant_button.png")!), QCategoryy(name: "gym", image: UIImage(named: "gym_button.png")!), QCategoryy(name: "spa", image: UIImage(named: "spa_button.png")!), QCategoryy(name: "museum", image: UIImage(named: "museum_button.png")!)]
return list
}
static let searchApiHost = "https://maps.googleapis.com/maps/api/place/nearbysearch/json"
static let googlePhotosHost = "https://maps.googleapis.com/maps/api/place/photo"
static let detailsApiHost = "https://maps.googleapis.com/maps/api/place/details/json"
static func getNearbyPlaces(by category:String, coordinates:CLLocationCoordinate2D, radius:Int, token: String?, completion: #escaping (QNearbyPlacesResponse?, Error?) -> Void) {
var params : [String : Any]
if let t = token {
params = [
"key" : AppDelegate.googlePlacesAPIKey,
"pagetoken" : t,
]
} else {
params = [
"key" : AppDelegate.googlePlacesAPIKey,
"radius" : radius,
"location" : "\(coordinates.latitude),\(coordinates.longitude)",
"type" : category.lowercased()
]
}
Alamofire.request(searchApiHost, parameters: params, encoding: URLEncoding(destination: .queryString)).responseJSON { response in
if let error = response.error {
completion(nil, error)
}
if let response = QNearbyPlacesResponse(dic : response.result.value as? [String : Any]) {
completion(response, nil)
}
else {
completion(nil, QNearbyPlacesResponseError.noParsingDone)
}
}
}
static func googlePhotoURL(photoReference:String, maxWidth:Int) -> URL? {
return URL.init(string: "\(googlePhotosHost)?maxwidth=\(maxWidth)&key=\(AppDelegate.googlePlacesAPIKey)&photoreference=\(photoReference)")
}
}
enum QNearbyPlacesResponseError : Error {
case noParsingDone
}
struct QNearbyPlacesResponse {
var nextPageToken: String?
var status: String = "NOK"
var places: [QPlace]?
init?(dic:[String : Any]?) {
nextPageToken = dic?["next_page_token"] as? String
if let status = dic?["status"] as? String {
self.status = status
}
if let results = dic?["results"] as? [[String : Any]]{
var places = [QPlace]()
for place in results {
places.append(QPlace.init(placeInfo: place))
}
self.places = places
}
}
func canLoadMore() -> Bool {
if status == "OK" && nextPageToken != nil && nextPageToken?.characters.count ?? 0 > 0 {
return true
}
return false
}
}
and also this class
import UIKit
import CoreLocation
import GooglePlaces
private let geometryKey = "geometry"
private let locationKey = "location"
private let latitudeKey = "lat"
private let longitudeKey = "lng"
private let nameKey = "name"
private let openingHoursKey = "opening_hours"
private let openNowKey = "open_now"
private let vicinityKey = "vicinity"
private let typesKey = "types"
private let photosKey = "photos"
private let ratingKey = "rating"
private let urlKey = "url"
private let priceLevelKey = "price_level"
private let websiteKey = "website"
private let place_idKey = "place_id"
class QPlace: NSObject {
var location: CLLocationCoordinate2D?
var name: String?
var photos: [QPhoto]?
var vicinity: String?
var isOpen: Bool?
var types: [String]?
var rating: Float?
var url: String?
var price_level: Int?
var website: String?
var place_id: String?
init(placeInfo:[String: Any]) {
// coordinates
if let g = placeInfo[geometryKey] as? [String:Any] {
if let l = g[locationKey] as? [String:Double] {
if let lat = l[latitudeKey], let lng = l[longitudeKey] {
location = CLLocationCoordinate2D.init(latitude: lat, longitude: lng)
}
}
}
// name
name = placeInfo[nameKey] as? String
// opening hours
if let oh = placeInfo[openingHoursKey] as? [String:Any] {
if let on = oh[openNowKey] as? Bool {
isOpen = on
}
}
// vicinity
vicinity = placeInfo[vicinityKey] as? String
// types
types = placeInfo[typesKey] as? [String]
// rating
rating = placeInfo[ratingKey] as? Float
url = placeInfo[urlKey] as? String
//priceLevel
price_level = placeInfo[priceLevelKey] as? Int
//website
website = placeInfo[websiteKey] as? String
place_id = placeInfo[place_idKey] as? String
// photos
photos = [QPhoto]()
if let ps = placeInfo[photosKey] as? [[String:Any]] {
for p in ps {
photos?.append(QPhoto.init(photoInfo: p))
}
}
}
func getDescription() -> String {
var s : [String] = []
if let types = types {
s.append("\(types.joined(separator: ", "))")
}
if let rating = rating {
s.append("Rating: \(rating)")
}
if let price_level = price_level {
s.append("Price level: \(price_level)")
}
if let isOpen = isOpen {
s.append(isOpen ? "OPEN NOW" : "CLOSED NOW")
}
if let url = url {
s.append("\(url)")
}
if let website = website {
s.append("\(website)")
}
if let vicinity = vicinity {
s.append("\(vicinity)")
}
return s.joined(separator: "\n")
}
func getDescriptionMaps() -> String {
var m : [String] = []
if let vicinity = vicinity {
m.append("\(vicinity)")
}
return m.joined(separator: "\n")
}
func heightForComment(_ font: UIFont, width: CGFloat) -> CGFloat {
let desc = getDescription()
let rect = NSString(string: desc).boundingRect(with: CGSize(width: width, height: CGFloat(MAXFLOAT)), options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
return ceil(rect.height)
}
}
my only problem is that i can not download website and price level of the places, so looked in the documentation of google places i found out that my problem is caused because searhAPI (https://developers.google.com/places/web-service/search) does not retrive these information about places, so i have to use https://developers.google.com/places/web-service/details to get place's website and price level, but i need an help because i don't know how to do or how i have to modify my classes to make all this work and able my Class QPlace to get also website and price level of the places.

How to handle array in model class?

How can make model class for this json data
{
total: 41,
totalPages: 4,
valueData: [
{
id: "23",
lastLogin: "0-Jul-2011 11:27:36 AM",
name: "varii"
},
{
id: "24",
lastLogin: "0-Jul-2015 11:27:36 AM",
name: "sarii"
},
{
id: "25",
lastLogin: "0-Jul-2018 11:27:36 AM",
name: "narii"
} ]
}
class OnResponse {
var total: Int?
var totalPages: Int?
init(response: [String: Any]) {
self.total = response["total"]
self.totalPages = response["totalPages"]
}
}
It's not working how can make it ready for work
and how to pass values to controller to model and how to get value from model
Follow the below class structure
class Response {
var total: Int?
var totalPages: Int?
var valueData: [LoginData]?
init(response: [String: Any]) {
self.total = response["total"]
self.totalPages = response["totalPages"]
var items:[LoginData] = ()
for (data in response["valueData"]) {
let login = LoginData(name: data["name"], lastLogin: data["lastLogin"])
items.append(login)
}
self.valueData = items
}
}
class LoginData {
var name: String?
var lastLogin: String?
init(name: String, lastLogin: String) {
self.name = name
self.lastLogin = lastLogin
}
}
you should use "reverseMatches" to retrieve the array, not the "data". May be you can use a third library to convert your json data to a model, such as Unbox, https://github.com/JohnSundell/Unbox.
Model for Your response :
struct ModelName {
var total: Int?
var totalPage: Int?
var reverseMatches: [LoginDetails]?
}
struct LoginDetails {
var id: String?
var lastLogin: String?
var name: String?
}
Parse the api response and assign the values on the appropriate fields. I have made, all the variables are optional.
Assign values like below.
var obj = Model()
obj.total = response["total"] as? Int
obj should be var, because you are going to mutate the struct values. because struct is value based, not reference based
class DataModel{
var total : Int?
var totalPages : Int?
var valueData : [UserModel]?
init(JSON: [String:Any?]){
self = parser.doParse(JSON: JSON)
}
}
class UserModel{
var id : String?
var lastLogin : String?
var name : String?
}
class parser : NSObject{
class func doParse(JSON: [String:Any?])->DataModel{
let dataModel = DataModel()
dataModel.total = JSON["total"] as? Int
dataModel.totalPages = JSON["totalPages"] as? Int
var userInfo : [UserModel] = []
let valueData : [String:String?]? = JSON["valueData"] as? [String:String?]
if let valueData = valueData{
for dataDict : [String:String?] in valueData{
let itemModel = UserModel()
itemModel.id = dataDict["id"] as? String
itemModel.lastLogin = dataDict["lastLogin"] as? String
itemModel.name = dataDict["name"] as? String
userInfo.append(itemModel)
}
dataModel.valueData = userInfo
}
return dataModel
}
}
class LoginData: NSObject {
let total: Int = 0
let totalPages: Int = 0
let valueData: Array<ValueData> = Array<ValueData>()
override init(total: Int!, totalPages: Int, valueData: Array<ValueData>!) {
self.total = total
self.totalPages = totalPages
self.valueData = valueData
}
}
struct ValueData {
let id: int?
let lastLogin: String?
let name: String?
init(id: int!, lastLogin: string, name: string!)
self.id = id ?? 0
self.lastLogin = lastLogin ?? ""
self.name = name ?? ""
}
}
you should use struct instead of class for creating model object...
advantages of struct over class refer
Why Choose Struct Over Class?
class/24232845
use two struct for holding your data one is for your single total count
and other is for last login detail
struct lastLogin {
let totalCount: (total: Int, totalPages: Int)
let valueArray: [lastLoginDetail]
}
struct lastLoginDetail {
let valueData: (id: String, lastLogin: String,name: String)
}
extension lastLoginDetail {
init(json: [String : String]) throws {
let id = json["id"]
let lastLogin = json["lastLogin"]
let name = json["name"]
let value = (id,lastLogin,name)
self.valueData = value as! (id: String, lastLogin: String, name: String)
}
}
extension lastLogin {
init(json: [String : Any]) throws {
let total = (json["total"] as! NSNumber).intValue
let totalPages = (json["totalPages"] as! NSNumber).intValue
let totalCounts = (total,totalPages)
var userInfo : [lastLoginDetail] = []
// Extract and validate valueData
let valueData = json["valueData"] as? NSArray
if let valueData = valueData{
for dataDict in valueData{
let dic : [String : String] = dataDict as! [String : String]
let lastLoginDeatails = try! lastLoginDetail(json: dic)
userInfo.append(lastLoginDeatails)
}
}
self.valueArray = userInfo
self.totalCount = totalCounts
}
}
func HowToUseModelClass(){
let jsonDic = NSDictionary()
// jsonDic // your json value
let dataValue = try! lastLogin(json: (jsonDic as! [String : Any])) // parsing the data
print(dataValue.totalCount.total)
print(dataValue.totalCount.totalPages)
print(dataValue.valueArray[0].valueData.id)
}

Resources