Filtering multidimensional array in uitableview - swift - swift

Here is my model
class BusinessProfile: NSObject {
var title: String?
var website: String?
var associatedOrganization: String?
var companyName: String?
var productList: [BusinessProfileProduct]?
}
class BusinessProfileProduct: NSObject{
var productName: Double?
var modelNumber: String?
var hsCode: String?
}
Here are my array variables in view controller.
var businessProfileArray = [BusinessProfile]()
var tempBusinessProfileArray = [BusinessProfile]()
I already have filtered businessProfileArray on the basis of companyName like below:
tempBusinessProfileArray = businessProfileArray.filter({ (BusinessProfile) -> Bool in
return (BusinessProfile.companyName!.lowercased().contains(searchText.lowercased()))
})
But I am unable to filter businessProfileArray on the basis of productName or hsCode from nested array of BusinessProfileProduct.
Note: businessProfileArray contains array of businessProfileProduct
Any help from anyone? Thanks in advance.

You can do something similar to this
func filterArr(searchText:String) -> [BusinessProfile] {
var filteredArr = [BusinessProfile]()
for profile in businessProfileArray {
var isMatched = false
if let matchedProduct = profile.productList.filter ({$0.companyName.lowercased().contains(searchText.lowercased())}).first {
isMatched = true
print(matchedProduct.companyName)
}
if isMatched {
filteredArr.append(profile)
}
}
return filteredArr
}
this will return all the profiles in which there is a match of searchText with product's companyName however it will not remove the extra products which does not match the searchText

Related

Overlapping accesses to 'but modification requires exclusive access; consider copying to a local variable'

an employee can own more than one animal, but an animal must have only one employee.
First, I add an employee. Then when adding an animal, I select the employee.
After selecting the employee, I press the save button.
I want to transfer the animal I just added to that employee.
I want to take the employee's id and add it to the employee attribute of the animal I added.
Animal Model:
struct Animal {
let id: String?
var name: String?
var sound: String?
var age: Double?
var waterConsumption: Double?
var employee: Employee?
var animalType: String?
}
Employee Model:
struct Employee {
let id: String?
var name: String?
var lastName: String?
var age: Int?
var gender: String?
var salary: Double?
var experienceYear: String?
var animals: [Animal]?
}
ZooManager:
Error is on this line: newAnimal.employee?.animals?.append(newAnimal)
Overlapping accesses to 'newAnimal.employee', but modification requires exclusive access; consider copying to a local variable
protocol ZooManagerProtocol {
var animals: [Animal] { get set }
var employees: [Employee] { get set }
func addNewAnimal(_ model: Animal)
}
class ZooManager: ZooManagerProtocol {
static var shared = ZooManager()
var animals: [Animal] = []
var employees: [Employee] = []
private init() { }
func addNewAnimal(_ model: Animal) {
var newAnimal = model
guard var employee = employees.filter({ $0.id == model.employee?.id }).first else { return }
newAnimal.employee = employee
newAnimal.employee?.animals?.append(newAnimal)
dump(newAnimal)
}
func addNewEmployee(_ model: Employee) {
employees.append(model)
}
}
The solution recommended by the compiler works
func addNewAnimal(_ model: Animal) {
var newAnimal = model
guard var employee = employees.filter({ $0.id == model.employee?.id }).first else { return }
employee.animals?.append(newAnimal)
newAnimal.employee = employee
}
I've never seen this error before, hopefully someone can add a good answer explaining the why and not just the how!
EDIT:
Here's the why https://github.com/apple/swift-evolution/blob/main/proposals/0176-enforce-exclusive-access-to-memory.md

Inner filtering of array doesn't filter swift

I am trying to filter an array of structs that has array. Below are the data structures I am using. I want the inner array filtered also but it doesn't work
var objects = [SomeObject]() //array of objects
var filteredObject = [SomeObject]() //filtered array
var isSearching = false
struct SomeObject {
var sectionName: String
var sectionObjects : [History]
}
struct History {
var firstName: String
var lastName: Int
}
func searchBar(_ text: String) {
filteredObject = objects.filter({ (obj: SomeObject) -> Bool in
return obj.sectionObjects.filter { $0.firstName.contains(text.lowercased())}.isEmpty
})
print("====", filteredObject, "fill===")
}
let history = History(firstName: "John", lastName: 1)
let anotherHistroy = History(firstName: "Dee", lastName: 2)
let historyArray = [history, anotherHistroy]
let newObject = SomeObject(sectionName: "Section 1", sectionObjects: historyArray)
objects.append(newObject)
searchBar("Jo") // printing of filtered object should not have another history in it's sectionObjects
You might be looking for something like this:
func searchBar(_ text: String) {
filteredObject = []
for var ob in objects {
ob.sectionObjects = ob.sectionObjects.filter {
$0.firstName.contains(text)
}
if !ob.sectionObjects.isEmpty {
filteredObject.append(ob)
}
}
print("====", filteredObject, "fill===")
}
Could perhaps be done more elegantly with reduce(into:), but on the whole it is best to start simply by saying exactly what you mean. You can tweak as desired to take account of case sensitivity.

Merge two arrays into one by key

I have two Arrays. I want to use them, to create the new one. Then, the new list will be use in the List View. So how it looks like? I have two models that are the basis for lists:
Photo:
class Photo: Object, Identifiable, Decodable {
#objc dynamic var albumId: Int = 0
#objc dynamic var id: Int = 0
#objc dynamic var title: String = ""
#objc dynamic var url: String = ""
#objc dynamic var thumbnailUrl: String = ""
override static func primaryKey() -> String? {
return "id"
}
}
Album:
class Album: Object, Decodable {
#objc dynamic var id: Int = 0
#objc dynamic var userId: Int = 0
#objc dynamic var title: String = ""
override static func primaryKey() -> String? {
return "id"
}
}
The child of both is ListItem. So the vars of the ItemLists are using vars from the Photo and Album classes. I hope I explained it well. If not, please ask.
class ListItem {
var id: Int = 0 Album id
var title: String = "" Photo title
var albumTitle: String = "" Album title
var thumbnailUrl: String = "" Photo thumbnailUrl
}
You can use zip to merge the 2 arrays together, then call map to create ListItems from each element.
let list = zip(photos, albums).map { photo, album in ListItem(id: album.id, title: photo.title, albumTitle: album.title, thumbnailUrl: photo.thumbnailUrl)}
If I understand your question directly you have a one to many relationship between Album and Photo so for each Photo we should look up the correct Album and create a new ListItem
This can be done using this code
let list: [ListItem] = photoArray.compactMap { photo in
guard let album = albumArray.first(where: { $0.id == photo.id }) else {
return nil
}
return ListItem(id: album.id, title: photo.title, albumTitle: album.title, thumbnailUrl: photo.url)
}

How to filter a Model which having nested array in swift

I am having model
class Consumer360PurchaseHistoryDetails: Codable {
var freqofPurchase : String?
var freqofVisit : String?
var customerPurchaseHistory : [CustomerPurchaseHistory]?
init() {
}
}
class CustomerPurchaseHistory : Codable {
var dateOfPurchase : String?
var products : [PurchaseProducts]?
init() {
}
}
class PurchaseProducts : Codable {
var productID : String?
var productFilterType : String?
init() {
}
}
I want to filter this model by productFilterType in PurchaseProducts
I tried the below way
var dataModel: Consumer360PurchaseHistoryDetails?
var tempDataModel:Consumer360PurchaseHistoryDetails = Consumer360PurchaseHistoryDetails()
for purchaseHistory in self.dataModel?.customerPurchaseHistory ?? [] {
for product in purchaseHistory.products ?? [] {
if product.productFilterType?.lowercased() == StringConstants.purchase {
tempDataModel.freqofVisit = "three"
tempDataModel.freqofPurchase = "five"
tempDataModel.customerPurchaseHistory?.append(purchaseHistory)
}
}
}
self.purchaseHistoryTableView.dataModel = self.tempDataModel
But the purchaseHistory is not getting appended in customerPurchaseHistory, which is is always nil after appending. But the freqofVisit and freqofPurchase is getting updated. Am i want use index for appending?
Your tempDataModel.customerPurchaseHistory? is set to nil by default. So the below code won't be executed.
tempDataModel.customerPurchaseHistory?.append(purchaseHistory)
Just above your for loop, assign its value to empty array, like this:
tempDataModel.customerPurchaseHistory = []
So, your code looks like this:
var dataModel: Consumer360PurchaseHistoryDetails?
var tempDataModel:Consumer360PurchaseHistoryDetails = Consumer360PurchaseHistoryDetails()
tempDataModel.customerPurchaseHistory = []
for purchaseHistory in self.dataModel?.customerPurchaseHistory ?? [] {
for product in purchaseHistory.products ?? [] {
if product.productFilterType?.lowercased() == StringConstants.purchase {
tempDataModel.freqofVisit = "three"
tempDataModel.freqofPurchase = "five"
tempDataModel.customerPurchaseHistory?.append(purchaseHistory)
}
}
}
self.purchaseHistoryTableView.dataModel = self.tempDataModel

Delegate #State value to a child component

A Swift new guy here. Trying to understand SwiftUI.
I'm trying to create a "Field" component that wraps a Text and TextField. The idea is to have less code and have a control that can display the field title and its corresponding value.
I can't figure out how to assign the value of the model on my control.
This is my model:
import Foundation
class EmployeeModel {
var FullName: String = "John Doe"
var JobStartDate: String = ""
var BornDate: String = ""
var DepartmentId: Int = 0
var DepartmentName: String = ""
var isBossDepartment: Bool = false
var JobPositionId: Int = 0
var JobPositionName: String = ""
var PersonalDocNumber: String = ""
var Password: String = ""
init() {
}
}
In some part of the view...
struct EmployeeView : View {
#State private var Employee = EmployeeModel()
var body : some View {
Field("Full Name", $Employee.FullName)
}
}
This is my custom component that want to implement.
struct Field : View {
private var caption: String = ""
#State private var controlValue: String = ""
init(caption: String, value: String) {
self.caption = caption
controlValue = value
}
var body : some View {
VStack {
Text(self.caption)
TextField($controlValue)
.textFieldStyle(.roundedBorder)
}
}
}
Currently I got a message of
'Binding' is not convertible to 'String' on my Field implementation of the EmployeeView
Before going into the details of your problem, please be advised that by convention, types (classes, structs, enums, etc) should begin with a capital letter, while objects and values should start with a lower case (or underscore). By not following that convention, you make the code hard to read for others, as everyone is expecting it.
Now, on your code, there are several improvements:
controlValue must be declared as #Binding.
#Binding properties should not have an initial value, as they are supposed to be passed by the caller.
If you declare your properties non-private, you wouldn't need an initializer. Nothing wrong with that, but if you do use an initializer, there are multiple changes you need to perform (see the code below).
Your TextField is using a deprecated and discontinued initializer in beta 5.
struct EmployeeModel {
var fullName: String = "John Doe"
var jobStartDate: String = ""
var bornDate: String = ""
var departmentId: Int = 0
var departmentName: String = ""
var isBossDepartment: Bool = false
var jobPositionId: Int = 0
var jobPositionName: String = ""
var personalDocNumber: String = ""
var password: String = ""
init() {
}
}
struct EmployeeView : View {
#State private var employee = EmployeeModel()
var body : some View {
Field(caption: "Full Name", value: $employee.fullName)
}
}
struct Field : View {
private var caption: String = ""
#Binding private var controlValue: String
init(caption: String, value: Binding<String>) {
self.caption = caption
self._controlValue = value
}
var body : some View {
VStack {
Text(self.caption)
TextField("", text: $controlValue)
.textFieldStyle(.roundedBorder)
}
}
}