Filter / Search function on Array of Structs, to display within tableView - swift

I have created my own Struct object, that is populated from the Photos framework with each "moment" an image was contained in, and other metadata.
struct MomentImage {
var momentNo : Int
var index : Int
var image : UIImage
}
I would like to populate a tableView, split with:
Sections, which map to momentNo
Cells, which map to index
In tableView(cellForRowAt indexPath), I have printed out:
print("location: \(indexPath.section) and \(indexPath.row)")
But the Section value only ever prints "0". I expected my print statements to occur through all the sections in this table (with my test data, this is actually 13 sections)
How do I filter / retrieve this object at these 2 indices from my array of var moments = [MomentImage]...
Using array.filter { } seems the most obvious way. How do I pass in a 2nd property to check (i.e. momentNo AND index) within the same call?
let filteredassets = assets.filter { $0.momentNo == indexPath.section }
is my current code.
Note, the images need to be encodable, hence extracting from the PHImageManager into a distinct UIImage within a Struct that should itself be encodable.

As provided by #Vicaren, applying a && after the 1st parameter converts into to a 2-property IF AND statement
$0 is still available to select, simply choose another available property to test.
My own example results in:
let filteredassets = assets.filter { $0.momentNo == indexPath.section && $0.index == indexPath.row }

Related

Swift how to remove elements from an array of enums?

I have an array of enums and I'd like to remove x number of elements from it, the code below is what I'm trying to achieve, it partially works because it removes elements only from the more variable created in the switch-case but the original array doesn't change
MyArray of enums
Contacts
More
if more is present it means there are more contacts to download, when the user tap on a button it should remove ids that has been downloaded
Here is an example:
// Switch last element of my array
switch model.myArray[model.myArray.count-1] {
// If last element is More type
case var more as More:
// Download new contacts
downloadfunction()
// Remove 100 contacts that has been downloaded
let range = 0...99
more.peopleIds?.removeSubrange(range)
}
More structure
public struct More: Decodable, Identifiable {
public let id: String?
public var peopleIds: [String]?
I think the best way to check the type of the last element of the array is to cast it using an if var ...
if var more = model.myArray.last as? More {
and then change it and replace the old value in the array
if var more = myArray.last as? More, let array = more.peopleIds {
more.peopleIds = Array(array.dropFirst(100))
myArray[myArray.endIndex - 1] = more
}

Delete specific object from LinkingObjects list - Realm Swift

I am currently trying out Realm on a test project and I have been struggling with removing a specific object from a List. LensDBObject and ListDBObject. LensDBObject contains a list of lenses and ListDBObject are lists of existing lenses. A lens can be in multiple lists and I'd like to remove a specific lens from a specific list but not remove if from the other lists.
Below are my two classes:
#objcMembers class LensDBObject: Object {
dynamic var id = UUID().uuidString
dynamic var manufacturer = ""
dynamic var series = ""
dynamic var serial = ""
dynamic var isSelected = false
override static func primaryKey() -> String? {
return "id"
}
let objects = LinkingObjects(fromType: ListDBObject.self, property: "lensList")
}
#objcMembers class ListDBObject: Object {
dynamic var listName = ""
let lensList = List<LensDBObject>()
}
Below is my code to find a specific lens in the list I want. The values returned are what I expect.
let listToSearch = realm.objects(ListDBObject.self).filter("listName == %#", "List 542")
print(listToSearch)
let filteredResults = listToSearch[0].lensList.filter("manufacturer == %# AND series == %# AND serial == %#", "Panavision" , "Primo Prime", "407")
print(filteredResults)
However, when I try to delete filteredResults, it deletes it from the lensDBOject altogether. I just want to be able to delete this specific lens from this specific list.
try! realm.write {
realm.delete(filteredResults)
}
I tried using for loops to get the index of the lens in the list and then delete it directly from that. But it still deletes the lens everywhere.
Am I missing something? Should I be using a one-to-many relationship as opposed to a LinkingObject?
Thanks for you help!
Try something like this. You only want to remove the lens from the list, not delete it from the Realm.
try! realm.write {
filteredResults.forEach { lens in
if let index = listToSearch[0].lensList.index(of: lens) {
listToSearch[0].lensList.remove(at: index)
}
}
}
Note that this will remove from that one specific list all lenses that match your filter.
Edit: Updated to reflect Realm's custom List class.
The if let is required, because index(of:) could potentially return nil if the object is not found in the list. Additionally, we must do it one item at a time rather than getting all the indexes first, since removing an item would cause the index array to be wrong.

contextual type cgfloat cannot be used with array literal

I'm using UIImage+Gradient.swift file to add gradient to my label, but I get this error:
contextual type cgfloat cannot be used with array literal
I've reviewed some FAQ Q&A but I'm still confused.
Here is the code:
let components = colors.reduce([]) { (currentResult: [CGFloat], currentColor: UIColor) -> [CGFloat] in
var result = currentResult
let numberOfComponents = currentColor.cgColor.numberOfComponents
let components = currentColor.cgColor.components
if numberOfComponents == 2 {
result.append([components?[0], components?[0], components?[0], components?[1]])
} else {
result.append([components?[0], components?[1], components?[2], components?[3]])
}
return result
}
Lines which are giving error are these:
result.append([components?[0], components?[0], components?[0], components?[1]])
result.append([components?[0], components?[1], components?[2], components?[3]])
This error has to do with trying to make a variable that is not an array be set to an array. For example this would produce a similar error:
var myFavSnacks:String = ["Apples","Grasses","Carrots"] //gives similar error
In your case, it thinks you want to add an array of CGFloats to one index in your array, rather than adding a number of CGFloats to your array.
To add multiple items to an array at once use contentsOf: like this:
colors.append(contentsOf: ["red", "blue"])
//adds strings "red" and "blue" to an existing array of strings called colors
From documentation available here:
https://developer.apple.com/reference/swift/array

RxCocoa extra argument in call

I am trying to attach data to a UITableView. I have download the project form here and am using the code where data is attached to the tableView: http://yannickloriot.com/2016/01/make-uitableview-reactive-with-rxswift/:
Firstly I have created the following variable:
let currentQuestion: Variable<Taxi?> = Variable(nil)
I then try to do the following:
currentQuestion
.asObservable()
.bindTo(tableView.rx_itemsWithCellIdentifier("ChoiceCell", cellType: ChoiceCell.self)) { (row, element, cell) in
cell.choiceModel = element
}
.addDisposableTo(disposeBag)
But I am getting the following warning: 'Extra argument in call' on the line .bindTo. I have tried adding a new cell and get the same result. Not sure if it is relevant, but I have registered the cell.
I have read here that you can get this warning if the types of the arguments don't match: Swift - Extra Argument in call . However it looks like the arguments match fine.
I am new to Rx and was hope someone could help me understand what might be going wrong here. Thanks.
======
Edit
Here is my new code. I have tried rx_itemsWithCellIdentifier("ChoiceCell") alone and rx_itemsWithCellIdentifier("ChoiceCell", cellType: ChoiceCell.self):
let currentQuestion = Variable<[Taxi]>(taxis)
currentQuestion.asObservable()
.bindTo(tableView.rx_itemsWithCellIdentifier("ChoiceCell")) {(row, element, cell) in
cell.choiceModel = element
}.addDisposableTo(disposeBag)
Where I have used (taxis), it is an array of taxi items. See picture below:
Also once I have called .asObservable(), I have the following:
I managed to print these out by removing the line .bindTo. If I add that line back I get the same error as before.
IMPORTANT: I played around with code base from article I linked to earlier. If I remove from ChoiceCell I can replicate the same error:
// var choiceModel: ChoiceModel? {
// didSet {
// layoutCell()
// }
// }
From experience the extra argument in call message is most often given when you are trying to bind a variable with the wrong expected data type. The first issue is that you are trying to bind a single instance of Taxi to the tableview which is expecting a sequence of observables.
/**
Binds sequences of elements to table view rows.
- parameter cellIdentifier: Identifier used to dequeue cells.
- parameter source: Observable sequence of items.
- parameter configureCell: Transform between sequence elements and view cells.
- parameter cellType: Type of table view cell.
- returns: Disposable object that can be used to unbind.
*/
public func rx_itemsWithCellIdentifier<S : SequenceType, Cell : UITableViewCell, O : ObservableType where O.E == S>(cellIdentifier: String, cellType: Cell.Type = default) -> (source: O) -> (configureCell: (Int, S.Generator.Element, Cell) -> Void) -> Disposable
It doesn't seem like the issue is caused by the optional object but I don't see why you would want to bind optional objects to the tableview, so I would advice you to avoid that too. Here is an example which would work.
let currentQuestion = Variable<[Taxi]>([Taxi()])
currentQuestion.asObservable().bindTo(tableView.rx_itemsWithCellIdentifier("ChoiceCell")) {(row, element, cell) in
cell.choiceModel = element
}.addDisposableTo(disposeBag)
Thanks to Philip Laine answer above: https://stackoverflow.com/a/36536320/2126233
That helped me see that I was making a mistake in regards to what I was observing. It helped me to see the problem I was having in my code.
If you just want to bind to a normal tableViewCell then you need to use tableView.rx_itemsWithCellFactory:
currentQuestion.asObservable()
.bindTo(tableView.rx_itemsWithCellFactory) {(tableView, row, item) in
let cell = UITableViewCell()
cell.textLabel?.text = item.distance
return cell
}.addDisposableTo(disposeBag)
If you are using a custom cell then you can use tableView.rx_itemsWithCellIdentifier("ChoiceCell", cellType: ChoiceCell.self). Here is an example:
currentQuestion
.asObservable()
.filter { $0 != nil }
.map { $0!.choices }
.bindTo(tableView.rx_itemsWithCellIdentifier("ChoiceCell", cellType: ChoiceCell.self)) { (row, element, cell) in
cell.choiceModel = element
}
.addDisposableTo(disposeBag)
For me I was still getting this same error unless I had a property inside the tableView cell that matched the element that was coming out of the array I was binding the tableView to.
So if you have an array of [Taxis] like so then inside the tableViewCell I required a variable that stored a Taxis. Then I was able to compile my project.
So in ChoiceCell I have a var like so:
var taxi: Taxi? {
didSet {
layoutCell()
}
}
I hope this helps anyone else having issues binding a tableViewCell to an array.
Well, this is a late answer but here is your problem. If this can help others to understand it.
You have an array of Taxi elements
when rx_itemsWithCellIdentifier() is called, element is a Taxi (of course, it is an element of the array)
But in your code you do:
cell.choiceModel = element
// Where
var choiceModel: ChoiceModel? {
didSet {
layoutCell()
}
}
So, you are trying to assign a Taxi to a ChoiceModel. This is why your are getting this error. Swift type inference determines that the types mismatch. You can try to comment the line inside the block and the error should disappear.

Swift import dictionary to tableviewcell

I am trying to create a table view to show the content of a dictionary called tableText<String, String>. But in the cellForRowAtInSection method, when I do something like
let obj = tableText[indexPath.row]
It gives me a error saying that
cannot subscript a value of type 'Dictionary<String, String> with an index of Int.
I also tried things like
let obj = tableText[indexPath.row] as? NSDictionary
but the error is still there.
Could you help me with this please?
If you just want the values, you can get an array of values from the dictionary and used the indexPath.row on that array.
if let text = tableText.values[indexPath.row] as? String {
cell.textLabel.text = text
}
Obviously if you need them to be sorted you would have to sort them as dictionary is not sorted based on keys.
If you wanted them sorted alphabetically, you could do this
let sortedText = tableText.values.sorted { $0.localizedCaseInsensitiveCompare($1) == NSComparisonResult.OrderedAscending }
And use this sortedText array as your datasource