Type '()' cannot conform to 'View' - SwiftUI - swift

I recently started a beginner project and I have this annoying error. Basically I want to loop trough some tasks, take every progress and sum it.
import Foundation
import SwiftUI
class sums: ObservableObject{
#Published var sum: Double = 0
#EnvironmentObject var listViewModel: ListViewModel
func sums2()->Double{
ForEach(listViewModel.items){item in
sum += item.test
}
return sum
}
}

ForEach is a SwiftUI view designed for looping through a collection of objects, and then rendering SwiftUI views for each of them.
What your code seems to be looking for is looping through an array, and adding up a value from each. This is a function for your data rather than your UI, and can be achieved using pure Swift.
Swift offers a couple of looping options: for...in and .forEach. The former is useful for cases where you might need to skip options or exit a loop early; with .forEach you always access every element of the collection. For your case, .forEach fits the bill.
let sum = 0
listViewModel.items.forEach { item in
sum += item.test
}
return sum
In terms of general programming, I'm not quite sure why you declare sum as a property and then have a function which updates that property as well as returning a value. It feels like you're mixing the concepts of view models, helper methods and views in ways that are going to get you in all sorts of trouble.
Depending on how your view model is set up, it might be easier to declare a method within that to handle the summation:
class ListViewModel: ObservableObject {
#Published var items: ItemType
func sum() -> Double {
let sum = 0
items.forEach { sum += $0.test }
return sum
}
}
The pattern of "loop through a collection and return a single value based on all of them" is such a common one that we have a Swift function, reduce, that helps us:
func sum() -> Double {
items.reduce(0) { (accumulator, item) in
accumulator + item.test
}
// or in shorthand
items.reduce(0) { $0 + $1.test }
}
Good luck with the rest of your learning, and remember to keep your views separate from your data!

Related

Moving LazyFilterSequence out of ForEach into a separate variable results in requires conform to 'RandomAccessCollection'

This code works fine
ForEach(items.filter({$0.matchesSearch(text: searchText) }), id: \.id) { item in
but moving the collection out of the ForEach
let takenOut = items.filter({$0.matchesSearch(text: searchText) })
ForEach(takenOut, id: \.id) { item in
results in this error
Generic struct 'ForEach' requires that 'LazyFilterSequence<Results<T>>.SubSequence'
(aka 'LazyFilterSequence<Slice<Results<T>>>') conform to 'RandomAccessCollection'
any ideas why?
i need to take the sequence out so i can use it's count
whole code for the context
struct SearchSectionView<T: DatabaseItem & Identifiable & SearchMatchingSupport>: View {
#Binding var searchText: String
#ObservedResults(
T.self,
where: { $0.recordState == .published }
) var items
var body: some View {
Section("Results") {
let o = items.sorted(byKeyPath:"name", ascending: true).filter({$0.matchesSearch(text: searchText)})
ForEach(o, id: \.id) { item in
Label(item.suggestionText(), systemImage: "circle").searchCompletion("\(item.id)")
}
}
}
}
Why does assigning the result of filter to a let constant cause it to no longer work?
Short Answer:
filter returns different results based upon what the receiver expects.
Long Answer
Consider this example:
let arr = ["apple", "apricot", "banana", "pear"]
let filtered = arr.lazy.filter { $0.hasPrefix("a") }
func count(arr: [String]) -> Int {
arr.count
}
// This works
count(arr: arr.lazy.filter { $0.hasPrefix("a") })
// This doesn't work
count(arr: filtered)
Here it is running in a Playground. When filter is passed directly to count, it works. When filter is assigned to the constant filtered, it doesn't work when passed to count.
So, what is going on here?
In the first call to filter, it returns a LazyFilterSequence<String>.
When passed to count which is expecting a [String], filter returns a [String].
To make this example more similar to your case, let's make count generic:
func count<T: RandomAccessCollection>(arr: T) -> Int {
arr.count
}
Now, like ForEach, count is expecting something that adopts RandomAccessCollection. In this case, Swift still chooses the version of filter that returns an Array:
Conclusion
The conclusion is, there are different versions of filter, and the one you get is based upon the expectations of the receiver. In this example, when the result was assigned to filtered, Swift returned a version that required the least up front work. That way, if only the first item in the result is needed, a lot of work is saved.
In the second case where we are passing the filtered result to count(), Swift does the extra work to turn the result back into an Array which is what count expects.
In your case, ForEach expects something that adopts RandomAccessCollection. Swift chooses a version of filter that returns a value that adopts that protocol.
Note:
You can use let to store the search result, you just need to give your constant the explicit type that is passed to ForEach. For example, in my case:
let filtered: [String] = arr.lazy.filter { $0.hasPrefix("a") }
count(arr: filtered) // this now works
[Option]-click on your call to filter to find the type that filter is returning and then use that type in your let statement.
This filtering shouldn't be in the view, move it to your model, so that the view's model becomes sortedAndFilteredStuff and not stuff that the view has to then process itself. You'll get errors, sometimes hard-to-understand errors, if you put regular swift code inside a ViewBuilder block.

SwiftUI: Sort list based on an #Published variable

I'm building a UI with SwiftUI, and I have an array that I use to build a List element. Now I want to sort that list based on a #Published variable coming from an #EnvironmentObject.
Approach 1
I tried getting the array already sorted, passing in the environment object to the sorting method:
List(getArraySorted(environmentObject)) { item in
//do stuff with item
}
This will compile, but the list will not update if environmentObject changes. I also tried passing in environmentObject.variableToBaseSortOn but to no avail.
Approach 2
I tried sorting the array inline in Swift UI:
List(array.sorted(by: { (lhs, rhs) -> Bool in
// do sorting based on self.environmentObject
})) { item in
// do stuff with item
}
This will compile, but crash:
Fatal error: No ObservableObject of type EnvironmentObjectClass found.
A View.environmentObject(_:) for EnvironmentObjectClass may be missing as an ancestor of this view.
The hint in the crash is incorrect, environmentObject is set and self.environmentObject is set to the correct object.
Your best approach is probably to sort the list within ObservableObject each time the data changes using a property observer, then manually informing the ObservableObject publisher of the change (rather than using #Published, so the unsorted array is not briefly published between changes.)
It's not a good idea to do the sorting directly in the body of the View because that block may be called many, many times as SwiftUI renders it (this is transparent to us as developers). I suspect that may be a reason that Approach 2 is not working.
For example:
import Combine
class SomeObject: ObservableObject {
// or whatever data type you need...
var array: [String] {
didSet {
array.sort(by: { (lhs, rhs) -> Bool in
// do sorting based on self.environmentObject
})
objectWillChange.send()
}
}
}

how do people deal with iterating a Swift struct value-type property?

Here's an obvious situation that must arise all the time for people:
struct Foundation {
var columns : [Column] = [Column(), Column()]
}
struct Column : CustomStringConvertible {
var cards = [Card]()
var description : String {
return String(describing:self.cards)
}
}
struct Card {}
var f = Foundation()
for var c in f.columns {
c.cards.append(Card())
}
That code is legal but of course it has no effect on f, because var c is still a copy — the actual columns of f are unaffected.
I am not having any difficulties understanding why that happens. My question is what people generally do about it.
Clearly I can just make the whole matter go away by declaring Column a class instead of a struct, but is that what people usually do? (I'm trying to follow a mental stricture that one should avoid classes when there's no need for dynamic dispatch / polymorphism / subclassing; maybe I'm carrying that too far, or maybe there's something else people usually do, like using inout somehow.)
As of Swift 4, a compromise is to iterate over the indices of a mutable collection instead of the elements themselves, so that
for elem in mutableCollection {
// `elem` is immutable ...
}
or
for var elem in mutableCollection {
// `elem` is mutable, but a _copy_ of the collection element ...
}
becomes
for idx in mutableCollection.indices {
// mutate `mutableCollection[idx]` ...
}
In your example:
for idx in f.columns.indices {
f.columns[idx].cards.append(Card())
}
As #Hamish pointed out in the comments, a future version of Swift may implement a mutating iteration, making
for inout elem in mutableCollection {
// mutate `elem` ...
}
possible.

Swift memoizing/caching lazy variable in a struct

I drank the struct/value koolaid in Swift. And now I have an interesting problem I don't know how to solve. I have a struct which is a container, e.g.
struct Foo {
var bars:[Bar]
}
As I make edits to this, I create copies so that I can keep an undo stack. So far so good. Just like the good tutorials showed. There are some derived attributes that I use with this guy though:
struct Foo {
var bars:[Bar]
var derivedValue:Int {
...
}
}
In recent profiling, I noticed a) that the computation to compute derivedValue is kind of expensive/redundant b) not always necessary to compute in a variety of use cases.
In my classic OOP way, I would make this a memoizing/lazy variable. Basically, have it be nil until called upon, compute it once and store it, and return said result on future calls. Since I'm following a "make copies to edit" pattern, the invariant wouldn't be broken.
But I can't figure out how to apply this pattern if it is struct. I can do this:
struct Foo {
var bars:[Bar]
lazy var derivedValue:Int = self.computeDerivation()
}
which works, until the struct references that value itself, e.g.
struct Foo {
var bars:[Bar]
lazy var derivedValue:Int = self.computeDerivation()
fun anotherDerivedComputation() {
return self.derivedValue / 2
}
}
At this point, the compiler complains because anotherDerivedComputation is causing a change to the receiver and therefore needs to be marked mutating. That just feels wrong to make an accessor be marked mutating. But for grins, I try it, but that creates a new raft of problems. Now anywhere where I have an expression like
XCTAssertEqaul(foo.anotherDerivedComputation(), 20)
the compiler complains because a parameter is implicitly a non mutating let value, not a var.
Is there a pattern I'm missing for having a struct with a deferred/lazy/cached member?
Memoization doesn't happen inside the struct. The way to memoize is to store a dictionary off in some separate space. The key is whatever goes into deriving the value and the value is the value, calculated once. You could make it a static of the struct type, just as a way of namespacing it.
struct S {
static var memo = [Int:Int]()
var i : Int
var square : Int {
if let result = S.memo[i] {return result}
print("calculating")
let newresult = i*i // pretend that's expensive
S.memo[i] = newresult
return newresult
}
}
var s = S(i:2)
s.square // calculating
s = S(i:2)
s.square // [nothing]
s = S(i:3)
s.square // calculating
The only way I know to make this work is to wrap the lazy member in a class. That way, the struct containing the reference to the object can remain immutable while the object itself can be mutated.
I wrote a blog post about this topic a few years ago: Lazy Properties in Structs. It goes into a lot more detail on the specifics and suggest two different approaches for the design of the wrapper class, depending on whether the lazy member needs instance information from the struct to compute the cached value or not.
I generalized the problem to a simpler one: An x,y Point struct, that wants to lazily compute/cache the value for r(adius). I went with the ref wrapper around a block closure and came up with the following. I call it a "Once" block.
import Foundation
class Once<Input,Output> {
let block:(Input)->Output
private var cache:Output? = nil
init(_ block:#escaping (Input)->Output) {
self.block = block
}
func once(_ input:Input) -> Output {
if self.cache == nil {
self.cache = self.block(input)
}
return self.cache!
}
}
struct Point {
let x:Float
let y:Float
private let rOnce:Once<Point,Float> = Once {myself in myself.computeRadius()}
init(x:Float, y:Float) {
self.x = x
self.y = y
}
var r:Float {
return self.rOnce.once(self)
}
func computeRadius() -> Float {
return sqrtf((self.x * self.x) + (self.y * self.y))
}
}
let p = Point(x: 30, y: 40)
print("p.r \(p.r)")
I made the choice to have the OnceBlock take an input, because otherwise initializing it as a function that has a reference to self is a pain because self doesn't exist yet at initialization, so it was easier to just defer that linkage to the cache/call site (the var r:Float)

Map a list of one type to another in RxSwift but keep existing ones

I've been running into this every now and then I'm always questioning myself whether I'm using RxSwift (or reactive means altogether) the wrong way.
The challenge is converting value types to something representable on the UI.
Usually on the lower level I'm storing data with simple types but I need something more stateful on the UI level. Just to give you an example, consider I have list of following types:
struct Person {
let firstName: String
let lastName: String
}
On the UI, however, I'm binding view models created from these items into a UITableView instance. I could achieve this by simply mapping from one type to another:
let displayedPersons = listOfPersons.map { PersonViewModel($0) }
This would make all items to be recreated on each update which I'm trying to avoid. I'm using MVVM and would like to keep the view model instances due to their transient state. Reloading table view on each update would also mess up animations.
I'm thinking if a custom binding could help here, binding one observable to another with cached mapping. Another solution that I've ended up doing is simply looping the observable so that when mapping, I get the previous value which I'll use as a cache.
Effectively I would need to map only the new items and keep the existing ones. Any ideas how would I achieve this?
In my very biased opinion, MVVM is good only for very complex UI where elements need to update dynamically and independently from each other. For all other cases I use my own library https://github.com/maxvol/RaspSwift (NB: it is not only for UI, but for UI as well). The core idea stems from MVI, and boils down to having a new snapshot of the state on every mutating event. So in your case the state would contain a collection of cached PersonViewModel, which will be partially updated upon receiving mutating events. The whole thing would be bound to UITableView via RxDataSources library.
One simple solution that I'm currently using is simply to map and recycle the old items if they exist.
I've created an extension to make it work with sequences. In memoryLookup you will receive the previous values and can reuse any item from the previous round.
public extension ObservableType where E: Sequence {
public func mapWithMemory<R>(memoryLookup: #escaping (Self.E.Element, [R]) throws -> R?, transform: #escaping (Self.E.Element) throws -> R) -> RxSwift.Observable<[R]> {
return self.scan([]) { (acc, elements) -> [R] in
let mapped = try elements.map { e in
return try memoryLookup(e, acc) ?? transform(e)
}
return mapped
}
}
}
And here's an example usage where an array of Ints is mapped to array of Strings.
func testMapWithMemory() {
var creationCounts = [Int: Int]()
let items = Observable.from([[1, 2],[2, 3],[2, 3]])
let mapped = items.mapWithMemory(memoryLookup: { (item, previousItems) -> String? in
return previousItems.first { $0 == "\(item)" }
}) { (item) -> String in
creationCounts[item, default: 0] += 1
return "\(item)"
}
let xs = try! mapped.toBlocking().toArray().last!
XCTAssertEqual(xs, ["2", "3"])
XCTAssertEqual(creationCounts, [
1: 1,
2: 1,
3: 1
])
}
Use at your own risk. And feel free to improve and share.
Also note that this is only useful if you need to avoid creating new items. In my case I'm using classes and binding UI elements to these items, so I don't want to recreate those.