So I'm making a to-do list app, and I want the user to be notified when all of the shopping items have been deleted. I have a dictionary that contains the String:store as a key and the [String]:items as the values. Is there a fast way to check if all of the items' arrays are empty?
There's the easy way:
dicts.values.flatten().isEmpty
But that will walk through all the lists without any shortcuts. Usually that's really not a problem. But if you want to bail out when you find a non-empty one:
func isEmptyLists(dict: [String: [String]]) -> Bool {
for list in dicts.values {
if !list.isEmpty { return false }
}
return true
}
Of course you can make this much more generic to get short-cutting and convenience:
extension SequenceType {
func allPass(passPredicate: (Generator.Element) -> Bool) -> Bool {
for x in self {
if !passPredicate(x) { return false }
}
return true
}
}
let empty = dicts.values.allPass{ $0.isEmpty }
You can just use isEmpty
var dict: Dictionary<Int, String> = [:]
var result = dict.isEmpty
result will be true
A functional programming approach:
let allEmpty = arr.reduce(true) { $0 && $1.1.isEmpty }
If you're not a big fan of implicit closure arguments, you can of course name them:
let allEmpty = arr.reduce(true) { empty, tuple in empty && tuple.1.isEmpty }
Related
I'm using the swift package [Defaults][1] to manage a list of preferences in my app.
One of this property is an array of structures that I get using #Default(.list) var list.
In Swift UI, I loop on this list to edit the various elements.
#Default(.list) var list;
ForEach($list, id: \.wrappedValue.id) { element in
...
}
It's working fine and it works as expected.
My problem is that I need to filter this list.
I'm using $list.filter(...) but I get a warning Conformance of 'Binding<Value>' to 'Sequence' is only available in MacOS 12.0.
Unfortunately, my app needs to run on MacOS 11.x.
I don't really understand what the warning means and how I can adapt my code to make it works with MacOS 11.x.
Thanks!
-- Update --
struct Stock: Codable, Identifiable, DefaultsSerializable {
var id: String
var currency: String = "USD"
}
extension Defaults.Keys {
static let stocks = Key<[Stock]>("stocks", default: [])
}
struct StocksView: View {
#Default(.stocks) var stocks
func filterStocks(stock: Stock) -> Bool
{
return stock.currency == "USD"
}
var body: some View {
ForEach($stocks.filter(filterStocks, id: \.wrappedValue.id) { stock in
....
}
}
}
extension Binding where Value == [Stock] {//where String is your type
func filter(_ condition: #escaping (Stock) -> Bool) -> Binding<Value> {//where String is your type
return Binding {
return wrappedValue.filter({condition($0)})
} set: { newValue in
wrappedValue = newValue
}
}
}
[1]: https://github.com/sindresorhus/Defaults
When you use $list.filter your are not filtering list you're filtering Binding<[List]> which doesn't conform to the protocol Sequence.
Add this extension to your project and see if it works
extension Binding where Value == [String] {//where String is your type
func filterB(_ condition: #escaping (String) -> Bool) -> Binding<Value> {//where String is your type
return Binding {
return wrappedValue.filter({condition($0)})
} set: { newValue in
wrappedValue = newValue
}
}
}
Edit: Binding in a ForEach requires SwiftUI 3+
However, you can pass the normal array and whenever you need a Binding use this function:
extension Stock {
func binding(_ array: Binding<[Stock]>) -> Binding<Stock> {
Binding {
self
} set: { newValue in
guard let index = array.wrappedValue.firstIndex(where: {$0.id == newValue.id}) else {return}
array.wrappedValue[index] = newValue
}
}
}
So your code would look like this:
ForEach(stocks.filter(filterStocks), id: \.id) { stock in
....
SomeViewRequiringBinding(value: stock.binding(stocks))
}
How we can achieve this Filter in Swift.
I have exactly same problem and i am trying this way and i found this solution on stack overflow
but this is written in Javascript and i need code in Swift language.
Getting this error
Cannot convert value of type '[Model]' to closure result type
'GetModel'
My Code and Model
extension Array where Element == GetModel{
func matching(_ text: String?) -> [GetModel] {
if let text = text, text.count > 0{
return self.map{
$0.data.filter{
$0.name.lowercased().contains(text.lowercased())
}
}
}else{
return self
}
}
}
// MARK: - GetModel
struct GetModel: Codable {
let id: Int
let name: String
var data: [Model]
}
// MARK: - Model
struct Model:Codable {
let id: Int
let name: String
var isSelected: Bool? = nil
}
You are making two mistakes. First you are using map but you should be using filter. Second you are using filter when you should be using contains(where:). Note you can. use localizedStandardCompare instead of lowercasing your string.
Note: You shouldn't check if your string count is greater than zero. String has an isEmpty property exactly for this purpose.
To check whether a collection is empty, use its isEmpty property
instead of comparing count to zero. Unless the collection guarantees
random-access performance, calculating count can be an O(n) operation.
extension RangeReplaceableCollection where Element == GetModel {
func matching(_ text: String?) -> Self {
guard let text = text, !text.isEmpty else { return self }
return filter { $0.data.contains { $0.name.localizedStandardContains(text) } }
}
}
edit/update:
If you need to filter your GetModal data:
extension RangeReplaceableCollection where Element == GetModel, Self: MutableCollection {
func matching(_ text: String?) -> Self {
guard let text = text, !text.isEmpty else { return self }
var collection = self
for index in collection.indices {
collection[index].data.removeAll { !$0.name.localizedStandardContains(text) }
}
collection.removeAll(where: \.data.isEmpty)
return collection
}
}
I have two lists of UIViews, some of that UIViews have an accessibilityIdentifier most of them are nil.
I'm searching for a way to generate a tuple(or something) with the two UIViews from the lists that have the same accessibilityIdentifier.
The lists are not sorted or something.
Is there a way to not iterate multiple times through the second list to find every pair?
for view in firstViewList {
if view.accessibilityIdentifier != nil {
for secondView in secondViewList {
if secondView.accessibilityIdentifier != nil && secondView.accessibilityIdentifier == view.accessibilityIdentifier {
viewPairs.append((firstView: view, secondView: secondView))
}
}
}
}
I think this is not very efficient.
Make a dict that indexes both view lists by their ID, filter out the ones where the ID is nil, and then use the keys common to both dicts to create a new dict that indexes pairs of same-id views.
Here's a rough example (which I haven't compiled myself).
func makeDictByAccessibilityID(_ views: [UIView]) -> [AccessibilityID: UIView] {
return Dictionary(uniqueKeysWithValues:
firstViewList
.lazy
.map { (id: $0.accessibilityIdentifier, view: $0) }
.filter { $0.id != nil }
)
}
viewsByAccessibilityID1 = makeDictByAccessibilityID(firstViewList)
viewsByAccessibilityID2 = makeDictByAccessibilityID(secondViewList)
commonIDs = Set(viewsByAccessibilityID1.keys).intersecting(
Set(viewsByAccessibilityID2.keys)
)
let viewPairsByAccessibilityID = Dictionary(uniqueKeysWithValues:
commonIDs.lazy.map { id in
// Justified force unwrap, because we specifically defined the keys as being available in both dicts.
(key: id, viewPair: (viewsByAccessibilityID1[id]!, viewsByAccessibilityID2[id]!))
}
}
This runs in O(n) time, which is the best you can get for this problem.
I think you should first filtered your two arrays from the nil value then you can do like this
let tempfirstViewList = firstViewList.filter { (view) -> Bool in
view.accessibilityIdentifier != nil
}
var tempsecondViewList = secondViewList.filter { (view) -> Bool in
view.accessibilityIdentifier != nil
}
tempfirstViewList.forEach { (view) in
let filterdSecondViewList = tempsecondViewList.filter { (secondView) -> Bool in
secondView.accessibilityIdentifier == view.accessibilityIdentifier
}
if let sView = filterdSecondViewList.first {
viewPairs.append((firstView: view, secondView: sView))
//after add it to your tuple remove it from the temp array to not loop throw it again
if let index = tempsecondViewList.firstIndex(of: sView) {
tempsecondViewList.remove(at: index)
}
}
}
This solution creates an array of tuples with views from the first and second list respectively
var viewArray: [(UIView, UIView)]
firstViewList.forEach( { view in
if let identifier = view.accessibilityIdentifier {
let arr = secondViewList.filter( {$0.accessibilityIdentifier == identifier} )
arr.forEach( { viewArray.append((view, $0))})
}
})
I am using the below code to update my array. My requirement is that the new data does not exist, for that I am using the .contains method.
This method is not working properly when I change the viewcontroller and come again on same page. It returns false and I keep getting duplicate results due to that.
Is this an issue related to the custom object?
if let list = rooms as? [[String : AnyObject]]
{
// self.roomList = [Room]()
for item in list
{
let roomStr = json(from: item)
let roomObj : Room = Room(JSONString: roomStr!)!
if !self.roomList.contains(roomObj)
{
self.roomList.append(roomObj)
}
}
DispatchQueue.main.async {
//First sort by name and then sort by default room..
self.roomList.sort { $0.getRoomName()! < $1.getRoomName()! }
self.roomList.sort { $0.getDefaultRoom()! && !$1.getDefaultRoom()! }
self.LoadRoomsTableView.reloadData()
self.hideActivity()
}
}
Any idea how to solve it or any suggest on the effiecient way for add/update the data in array with swift.
You have to make sure your Room class/struct implements the protocol Equatable.
Example:
class Room {
let rooms: Int
let color: String
init(rooms: Int, color: String) {
self.rooms = rooms
self.color = color
}
}
extension Room: Equatable {
func ==(lhs: Room, rhs: Room) -> Bool {
return lhs.rooms == rhs.rooms && lhs.color == rhs.color
}
}
If you had a struct and Swift > 4.1+ you would not have this problem, as the == is kindly provided by Swift. Thanks Vadian for the reminder.
I am developing search textfield. I have an array of Strings that looks like this....
var keywords:[String] = ["dance, music and hippo", "apple juice, tomato"]
I tried to use function contains but its not doing well.
for i in 0..<keywordsArr.count {
if(!keywordsArr[i].contains(searchTextField.text!)){
print("removed \(keywordsArr[i])")
keywordsArr.removeAtIndex(i)
}
}
this is extension that i am using.
extension String {
func contains(find: String) -> Bool{
return self.rangeOfString(find) != nil
}
}
the result should be i am calling function with text: "music" and it should return index of that string
You can achieve this very succinctly, using filter, which returns an array with only elements for which the closure holds. You should also use optional binding to avoid using a force unwrap of searchTextField.text
if let text = searchTextField.text {
keywordsArr = keywordsArr.filter { $0.contains(text) }
}
First you shouldn't edit the array size inside the loop, because each time you iterate you recalculate its size with "keywordsArr.count", and that will cause an exception You can use those few lines to return the index of the string if it's contained in your array, or -1 if not. good luck
func doesContain(array:[String], searchString:String) -> Int {
let size = array.count
for i in 0..<size {
if array[i].rangeOfString(searchString) != nil {
print("exists")
print("removed \(array[i])")
return i
}
}
return -1
}
var keywords:[String] = ["dance, music and hippo", "apple juice, tomato"]
doesContain(keywords, searchString: "music")