ForEach in SwiftUI: Error "Missing argument for parameter #1 in call" - swift

I'm still trying to create a calendar app and ran into another problem I wasn't able to find a solution for online.
Xcode throws the error "Missing argument for parameter #1 in call" in line 2 of my code sample. I used similar code at other places before, but I can't find the difference in this one.
Also this code worked before and started throwing this error after I moved some code to the new View DayViewEvent after getting the error "The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions", which I hoped to fix it and would be clean code anyway.
For reference: events is an optional array of EKEvents (hence the first if) and selectedDate is (obviously) a Date.
Any help is greatly appreciated!
if let events = events {
ForEach(events, id: \.self) { event in
if !event.isAllDay {
DayViewEvent(selectedDate: selectedDate, event: event)
}
}
}

The body func is called on every state change so needs to be as fast as possible thus the data needed should already be processed. So need to filter your events in a func or block, e.g. in onAppear:
struct EventsView: View {
#State var events = [Events]()
var body: some View {
ForEach(events, id: \.eventIdentifier) { event in
DayViewEvent(event: event)
}
.onAppear {
events = EventsDB.shared.events(isAllDay: false)
}
}
}
And by the way, since you are using id: \.self because event is a reference type you need to ensure when retrieving the events that the same event always returns the same instance and not a new instance. If unique instances are returned then you need to instead use an id that is a unique property, e.g. id: \.eventIdentifier.
Also you should know that the SwiftUI declarative syntax inside body is not like normal programming, the ifs and for loops all generate some very complex code, e.g. _ConditionalContent when an if is used, read up on it here: https://swiftwithmajid.com/2021/12/09/structural-identity-in-swiftui/ or watch Apple's WWDC video Demystify SwiftUI.

Related

Trying to assign variable using + swiftUI

I'm creating a math app and am trying to assign the correct answer to a variable. I've tried these two options below
#State private var correctAnswer1: Int {
number1L1 + number2L1
}
With this, I receive the error "Property wrapper cannot be applied to a computed property".
if currentLevel == 1
{
Text(" \(number1L1)")
.font(.system(size:70))
Text("+ \(number2L1)")
.font(.system(size:70))
Text("____")
.font(.system(size:70))
correctAnswer = number1L1 + number2L1
}
With this I receive the error "Type () cannot conform to 'View'"
How do I assign this variable without running into an error?
You really have two questions here. In the first case you have a property that is a computed property.
private var correctAnswer1: Int {
number1L1 + number2L1
}
Says that every time you ask for yourObject.correctAnswer1 the system should compute its value by running the code in the block.
Something a property that is attributed with #State, in contrast, is a box that holds a single value. You don't have a single value, you have a mechanism for computing a value. The compiler complains.
I the second case, you're laying out a View.
When you are working inside the body block of a SwiftUI View you are in a special environment called a ViewBuilder. Within that environment, each expression is expected to return some kind of View. The compiler collects them in a special way and will combine them together to make a user interface. (The environment also allows you some control-flow structures like if statements that are treated specially. If you are really curious you can search the web for information about Swift Result Builders)
In your case, you've included an expression that doesn't return a View in a place where Swift expects each expression to return one and Swift complains.

What is the purpose of objectWillChangeSequence compared to the onChange modifier in SwiftUI?

The sample comes from a WWDC22 video called The SwiftUI cookbook for navigation.
A NavigationModel is created to store the navigation path.
When the view appears, the NavigationModel is loaded with SceneStorage's data if any exists.
Whenever the NavigationModel changes, its data representation is saved in SceneStorage, by watching a custom objectWillChangeSequence computed property.
This last point intrigues me: why not just use the .onChange modifier? Like this:
.onChange(of: navModel.path) { _ in
data = navModel.jsonData
}
NB: the objectWillChangeSequence property is defined like this:
var objectWillChangeSequence: AsyncPublisher<Publishers.Buffer<ObservableObjectPublisher>> {
objectWillChange.buffer(size: 1, prefetch: .byRequest, whenFull: .dropOldest).values
}
There are lots of mistakes in the sample shown in the video.
One mistake is the Recipe struct has let id = UUID() which means even if the navigation path is restored, the recipe that was previously viewed would never be found because it has a different id from when it was persisted.
We can quickly fix it with this:
struct Recipe: Hashable, Identifiable {
var id: String {
return name
}
Now the #SceneStorage will start working and we can test replacing it with .onChange.
We quickly find out that NavigationModel has more than recipePath, there is also selectedCategory and columnVisibility. If we were to use onChange with jsonData then we would be needlessly doing an encode just to check if there has been a change.

SwiftUI nested API JSON call

I'm working on a little SwiftUI Project with some data, hosted by my own local network.
When the view appears, I'm calling the QuestionApiCall().getAllQuestions method which returns an Array of Question Objects. So far so good.
In every Question Object inside of the returned Array, there is a variable called senderId.
This variable is necessary for calling another Method: UserByApiCall(id: senderId).getUserById for getting the name of the User which asked the Question.
My idea was:
ScrollView(showsIndicators: false) {
VStack {
ForEach(questions) {question in
QuestionView(name: ?, content: question.content, creationTime: question.creation)
}
}
}
.onAppear() {
QuestionApiCall().getAllQuestions { (questions) in
self.questions = questions
}
Displaying the Question works properly but I don't know how to display their names, which can only displayed when calling the UserByApiCall(id: senderId).getUserById every time it loops threw the questions-Array with the specific senderId.
In theory I need to execute the UserByApiCall(id: senderId).getUserById every time question gets looped in the ForEach-Loop with the question.senderId for getting the User Object for every Question to display their names.
I really got no Idea how to make it or where to call it.
I can't just edit the Api and their methods because its an exercise from my Internship.
If you really can´t change the API move the responsibility to call the name API to your QuestionView.
As you didn´t include your QuestionView I need to make a few asumptions about it.
Your name var should look like this:
#State private var name: String = ""
In the .onApear of QuestionView add your function to call the name:
.onApear{
if name == ""{
UserByApiCall(id: senderId).getUserById{[weak self] name in
self?.name = name
}
}
}
And finally make your VStack a LazyVStack:
LazyVStack {
ForEach(questions) {question in
QuestionView(name: ?, content: question.content, creationTime: question.creation)
}
}
The LazyVstack will ensure, that not all elements are loaded at once.

RxSwift, why using .never() not for testing

I am going through the tutorial:
https://marcosantadev.com/mvvmc-with-swift/
Which talks about MVVM-C design pattern. I have real trouble understanding of how and why .never() observable is used there (and in general why we would want to use .never() besides testing timeouts).
Could anyone give a reasonable example of .never() observable usage in swift code (not in testing) and explain why it is necessary and what are the alternatives?
I address all the actions from View to ViewModel. User taps on a button? Good, the signal is delivered to a ViewModel. That is why I have multiple input observables in ViewModel. And all the observables are optional. They are optional because sometimes I write tests and don't really want to provide all the fake observables to test some single function. So, I provide other observables as nil. But working with nil is not very convenient, so I provide some default behavior for all the optional observables like this:
private extension ViewModel {
func observableNavigation() -> Observable<Navigation.Button> {
return viewOutputFactory().observableNavigation ?? Observable.never()
}
func observableViewState() -> Observable<ViewState> {
return viewOutputFactory().observableViewState ?? Observable.just(.didAppear)
}
}
As you can see, if I pass nil for observableViewState I substitute it with just(.didAppear) because the ViewModel logic heavily depends on the state of view. On the other hand if I pass nil for observableNavigation I provide never() because I assume that non of the navigation button will ever be triggered.
But this whole story is just my point of view. I bet you will find your own place to use this never operator.
Maybe your ViewModel has different configurations (or you have different viewModel under the same protocol), one of which does not need to send any updates to its observers. Instead of saying that the observable does not exist for this particular case (which you would implement as an optional), you might want to be able to define an observable as a .never(). This is in my opinion cleaner.
Disclaimer - I am not a user of RxSwift, but I am assuming never is similar than in ReactiveSwift, i.e. a signal that never sends any value.
It's an open ended question, and there can be many answers, but I've found myself reaching for never on a number of cases. There are many ways to solve a problem, but recently, I was simplifying some device connection code that had a cascading fail over, and I wanted to determine if my last attempt to scan for devices yielded any results.
To do that, I wanted to create an observable that only emitted a "no scan results" event in the event that it was disposed without having seen any results, and conversely, emitted nothing if it did.
I have pruned out other details from my code to sake of brevity, but in essence:
func connect(scanDuration: TimeInterval) -> Observable<ConnectionEvent> {
let scan = scan(for: scanDuration).share(replay: 1)
let connection: Observable<ConnectionEvent> =
Observable.concat(Observable.from(restorables ?? []),
connectedPeripherals(),
scan)
.flatMapLatest { [retainedSelf = self] in retainedSelf.connect(to: $0) }
let scanDetector = scan
.toArray() // <-- sum all results as an array for final count
.asObservable()
.flatMap { results -> Observable<ConnectionEvent> in
results.isEmpty // if no scan results
? Observable.just(.noDevicesAvailable) // emit event
: Observable.never() } // else, got results, no action needed
// fold source and stream detector into common observable
return Observable.from([
connection
.filter { $0.isConnected }
.flatMapLatest { [retained = self] event -> Observable<ConnectionEvent> in
retained.didDisconnect(peripheral: event.connectedPeripheral!.peripheral)
.startWith(event) },
scanDetector])
.switchLatest()
}
For a counter point, I realized as I typed this up, that there is still a simpler way to achieve my needs, and that is to add a final error emitting observable into my concat, it fails-over until it hits the final error case, so I don't need the later error detection stream.
Observable.concat(Observable.from(restorables ?? []),
connectedPeripherals(),
scan,
hardFailureEmitNoScanResults())
That said, there are many cases where we may want to listen and filter down stream, where the concat technique is not available.

How to make basic bindings in ReactiveCocoa 3 and 4

I've been reading up on ReactiveCocoa v3 lately and I'm struggling with just setting up basic stuff. I've already read the changelog, the tests, the few SO questions and the articles by Colin Eberhardt on the subject. However, I'm still missing examples on basic bindings.
Let's say I have an app that presents the menu of the day. The app is using RAC3 and the MVVM pattern.
Model (Menu)
The model has one simple method for fetching todays menu. As for now, this don't do any network requests, it basically just creates a model object. The mainCourse property is a String.
class func fetchTodaysMenu() -> SignalProducer<Menu, NoError> {
return SignalProducer {
sink, dispoable in
let newMenu = Menu()
newMenu.mainCourse = "Some meat"
sendNext(sink, newMenu)
sendCompleted(sink)
}
}
ViewModel (MenuViewModel)
The view model exposes different String variables for letting the view controller show the menu. Let's just add one property for showing the main course.
var mainCourse = MutableProperty("")
And we add a binding for this property:
self.mainCourse <~ Menu.fetchTodaysMenu()
|> map { menu in
return menu.mainCourse!
}
ViewController (MenuViewController)
Last but not least, I want to present this main course in a view. I'll add a UILabel for this.
var headline = UILabel()
And finally I want to set the text property of that UILabel by observing my view model. Something like:
self.headline.text <~ viewModel.headline.producer
Which unfortunately does not work.
Questions
The method fetchTodaysMenu() returns a SignalProducer<Menu, NoError>, but what if I want this method to return a SignalProducer<Menu, NSError> instead? This would make my binding in my view model fail as the method now may return an error. How do I handle this?
As mentioned, the current binding in my view controller does not work. I've been playing around with creating a MutableProperty that represents the text property of the UILabel, but I never got it right. I also think it feels clumsy or verbose to have to create extra variables for each property I want to bind. This was not needed in RAC2. I intentionally also tried to avoid using DynamicProperty, but maybe I shouldn't? I basically just want to find the right way of doing RAC(self.headline, text) = RACObserve(self.viewModel, mainCourse);.
Any other feedback/guidance on how to make this basic setup is highly appreciated.
So, after writing this question Colin Eberhardt made a part 3 of his RAC3 blog post series which includes a interesting and very relevant example of using MVVM and RAC3. The post can be found here and the source code here.
Based on his work, I've managed to answer my own questions:
By taking a slightly different approach, I'm able to make the fetchTodaysMenu() return a SignalProducer<Menu, NSError> as wanted. Here's how what I then would do in my view model:
MenuService.fetchTodaysMenu()
|> observeOn(QueueScheduler.mainQueueScheduler)
|> start(next: { response in
self.mainCourse.put(response.mainCourse!)
}, error: {
println("Error \($0)")
})
It seems like there's no UIKit bindings yet as of RAC3 beta 4. Colin made some UIKit extensions himself to help him make these bindings I was looking for as well. These can be found here. Adding them to my project, made be able to do exactly what I wanted to:
self.mainCourse.rac_text <~ self.viewModel.mainCourse
Update May 25, 2015
After been working a lot more with ReactiveCocoa 3, I would like to answer 1) once again. By using catch, it's possible to do this in a more declarative manner. I ended up implementing a small helper function for this:
public func ignoreError<T: Any, E: ErrorType>(signalProducer: SignalProducer<T, E>) -> SignalProducer<T, NoError> {
return signalProducer
|> catch { _ in
SignalProducer<T, NoError>.empty
}
}
The function transforms any NSError to NoError making it possible to bind as I wanted to by doing MenuService.fetchTodaysMenu() |> ignoreError.
I open sourced my project as this might be a good starting point for others looking into ReactiveCocoa 3.0:
https://github.com/s0mmer/TodaysReactiveMenu
Update March 5, 2016
As highlighted in the comments, since Swift 2, the ignoreError function would now look like:
public func ignoreError() -> SignalProducer<Value, NoError> {
return flatMapError { _ in
SignalProducer<Value, NoError>.empty
}
}
Also, an extension library called Rex has also been made, where something similar has been added.