I am creating a simple Taskmanager for a schoolproject similar to apple's scrumdinger.
I created two filesafer for each struct. But if I try to pass them further I always get the error "Extra trailing closure passed in call". Can someone help me?
import SwiftUI
#main
struct Aufgaben_ManagerApp: App {
//#State private var tasks = MyTask.sampleData
//#State private var categories = Category.sampleData
#StateObject private var storeTask = TaskStore()
#StateObject private var storeCategory = CategoryStore()
#State private var errorWrapper: ErrorWrapper?
var body: some Scene {
WindowGroup {
NavigationView {
MainView(tasks: $storeTask.tasks, categories: $storeCategory.categories) {
Task {
do {
try await TaskStore.save(tasks: storeTask.tasks)
//try await CategoryStore.save(categories: storeCategory.categories)
} catch {
errorWrapper = ErrorWrapper(error: error, guidance: "Try again later.")
}
}
}
}
.task {
do {
storeTask.tasks = try await TaskStore.load()
// storeCategory.categories = try await CategoryStore.load()
} catch {
errorWrapper = ErrorWrapper(error: error, guidance: "TaskManager will load sample data and continue.")
}
}
.sheet(item: $errorWrapper, onDismiss: {
storeTask.tasks = MyTask.sampleData
// storeCategory.categories = Category.sampleData
}) { wrapper in
ErrorView(errorWrapper: wrapper)
}
}
}
}
It looks like the problem is further down the road. Maybe that the MainView cant accept the Bindings?
It looks like you have some logic that is outside the scope of the Task and Sheet modifiers. Specifically this code:
{ wrapper in
ErrorView(errorWrapper: wrapper)
}
Related
I have a custom CommandGroup (customPasteboardCommands - it replaces the built-in .pasteboard group) which I would like to render conditionally.
What is the correct syntax?
I tried two ways, none of them worked:
First
if viewModel.shouldUseCustomCommandGroup {
customPasteboardCommands
}
Error:
Closure containing control flow statement cannot be used with result
builder 'CommandsBuilder'
Second:
viewModel.shouldUseCustomCommandGroup ? customPasteboardCommands : EmptyCommands()
Error:
Result values in '? :' expression have mismatching types 'some
Commands' and 'EmptyCommands'
Full code:
App.swift
import SwiftUI
#main
struct macos_playgroundApp: App {
private let viewModel = ViewModel()
var body: some Scene {
WindowGroup {
Button("Toggle custom CommandGroup") {
viewModel.onToggleCustomCommandGroup()
}
}
.commands {
commands
}
}
#CommandsBuilder
var commands: some Commands {
emptyNewItemCommands
viewModel.shouldUseCustomCommandGroup ? customPasteboardCommands : EmptyCommands()
}
var emptyNewItemCommands: some Commands {
CommandGroup(replacing: .newItem) {}
}
var customPasteboardCommands: some Commands {
CommandGroup(replacing: .pasteboard) {
Button("Select All") {
print("Custom select all activated")
}
.keyboardShortcut("a", modifiers: [.command])
}
}
}
ViewModel.swift
import Foundation
class ViewModel: ObservableObject {
#Published var shouldUseCustomCommandGroup: Bool = false
func onToggleCustomCommandGroup() {
shouldUseCustomCommandGroup = !shouldUseCustomCommandGroup
}
}
I have an actor like this, which performs long, complex work constantly in the background:
actor Foo {
var field: [Bar]
struct Bar {
// ...
}
}
How do I update its field from a SwiftUI view?
I tried this, but got these errors:
import SwiftUI
struct MyView: View {
#StateObject
var foo: Foo
var body: some View {
Text("Field count is \(foo.field.count)") // 🛑 Actor-isolated property 'field' can not be referenced from the main actor
Button("Reset foo") {
foo.field = [] // 🛑 Actor-isolated property 'field' can not be mutated from the main actor
}
}
}
How do I access & mutate my actor from within a SwiftUI view?
The problem with accessing the field property of the actor is that it requires and await call, if the access is made outside of the actor. This means a suspension point in your SwiftUI code, which means that when the SwiftUI code resumes, it might no longer be executing on the main thread, and that's a big problem.
If the actor doesn't do background work, then Asperi's solution that uses #MainAction would nicely work, as in that case the SwiftUI accesses happen on the main thread.
But if the actor runs in the background, you need another sync point that runs code on the main thread, that wraps the Foo actor, and which is consumed by your view:
actor Foo {
private(set) var field: [Bar]
func updateField(_ field: [Bar]) {
self.field = field
}
struct Bar {
// ...
}
}
class FooModel: ObservableObject {
private let foo: Foo
#Published var field: [Foo.Bar] = [] {
didSet {
Task { await foo.updateField(field) }
}
}
init(foo: Foo) {
self.foo = foo
Task { self.field = await foo.field }
}
}
struct MyView: View {
#StateObject
var foo: FooModel
However this is only half of the story, as you'll need to also send notifications from Foo to FooModel when the value of field changes. You can use a PassthroughSubject for this:
actor Foo {
var field: [Bar] {
didSet { fieldSubject.send(field) }
}
private let fieldSubject: PassthroughSubject<[Bar], Never>
let fieldPublisher: AnyPublisher<[Bar], Never>
init() {
field = ... // initial value
fieldSubject = PassthroughSubject()
fieldPublisher = fieldSubject.eraseToAnyPublisher()
}
func updateField(_ field: [Bar]) {
self.field = field
}
struct Bar {
// ...
}
}
and subscribe to the published from the model:
class FooModel: ObservableObject {
private let foo: Foo
#Published var field: [Foo.Bar] = [] {
didSet {
Task { await foo.updateField(field) }
}
}
init(foo: Foo) {
self.foo = foo
Task { self.field = await foo.field }
foo.fieldPublisher.receive(on: DispatchQueue.main).assign(to: &$field)
}
}
As you can see, there's a non-trivial amount of code to be written, due to the fact that actors run on arbitrary threads, while your SwiftUI code (or any UI code in general) must be run only on the main thread.
I am making an app that requires me to use an ml model and is performing its calculation to get the output from the ml model inside a function, now I need to display that constant which stores the final output of that ml model inside the body property of my swift UI view so that I can present it inside Text() maybe and modify the way it looks Lil bit
all of this code is inside a single swift file
here is all the code
after making the changes it's still showing some error
struct newView: View {
let model = AudiCar()
#ObservedObject var values: impData
#State var price = ""
var body: some View {
Text("calculated price is " + price)
.onAppear {
calculatePrice()
}
func calculatePrice() { <- this is where it showing error, saying " Closure containing a declaration cannot be used with function builder 'ViewBuilder' "
do {
let AudiCarOutput = try model.prediction(model: String(values.nameSelection), year: Double(values.yearSelection), transmission: String(values.transmisssionSelec), mileage: Double(values.mileage), fuelType: String(values.fuelSelection))
let price = String(AudiCarOutput.price)
}
catch {
}
}
}
struct newView_Previews: PreviewProvider {
static var previews: some View {
newView(values: impData())
}
}
try something like this:
struct NewView: View {
let model = AudiCar()
#ObservevedObject var values: impData
#State var price = ""
var body: some View {
Text("calculated price is " + price)
.onAppear {
calculatePrice()
}
}
func calculatePrice() {
do {
...
price = String(AudiCarOutput.price)
}
catch {
...
}
}
}
For a Store/Factory/ViewModel pattern using Combine and SwiftUI, I'd like a Store protocol-conforming class to expose a publisher for when specified model object(s) change internal properties. Any subscribed ViewModels can then trigger objectWillChange to display the changes.
(This is necessary because changes are ignored inside a model object that is passed by reference, so #Published/ObservableObject won't auto-fire for Factory-passed Store-owned models. It works to call objectWillChange in the Store and the VM, but that leaves out any passively listening VMs.)
That's a delegate pattern, right, extending #Published/ObservableObject to passed-by-reference objects? Combing through combine blogs, books, and docs hasn't triggered an idea to what's probably a pretty standard thing.
Crudely Working Attempt
I thought PassthroughSubject<Any,Never> would be useful if I exposed a VM's objectWillChange externally, but PassthroughSubject.send() will fire for every object within the model object. Wasteful maybe (although the ViewModel only fires its objectWillChange once).
Attaching a limiter (e.g., throttle, removeDuplicates) on Ext+VM republishChanges(of myStore: Store) didn't seem to limit the .sink calls, nor do I see an obvious way to reset the demand between the PassthroughSubject and the VM's sink... or understand how to attach a Subscriber to a PassthroughSubject that complies with the Protcols. Any suggestions?
Store-Side
struct Library {
var books: // some dictionary
}
class LocalLibraryStore: LibraryStore {
private(set) var library: Library {
didSet { publish() }
}
var changed = PassthroughSubject<Any,Never>()
func removeBook() {}
}
protocol LibraryStore: Store {
var changed: PassthroughSubject<Any,Never> { get }
var library: Library { get }
}
protocol Store {
var changed: PassthroughSubject<Any,Never> { get }
}
extension Store {
func publish() {
changed.send(1)
print("This will fire once.")
}
}
VM-Side
class BadgeVM: VM {
init(store: LibraryStore) {
self.specificStore = store
republishChanges(of: jokesStore)
}
var objectWillChange = ObservableObjectPublisher() // Exposed {set} for external call
internal var subscriptions = Set<AnyCancellable>()
#Published private var specificStore: LibraryStore
var totalBooks: Int { specificStore.library.books.keys.count }
}
protocol VM: ObservableObject {
var subscriptions: Set<AnyCancellable> { get set }
var objectWillChange: ObservableObjectPublisher { get set }
}
extension VM {
internal func republishChanges(of myStore: Store) {
myStore.changed
// .throttle() doesn't silence as hoped
.sink { [unowned self] _ in
print("Executed for each object inside the Store's published object.")
self.objectWillChange.send()
}
.store(in: &subscriptions)
}
}
class OtherVM: VM {
init(store: LibraryStore) {
self.specificStore = store
republishChanges(of: store)
}
var objectWillChange = ObservableObjectPublisher() // Exposed {set} for external call
internal var subscriptions = Set<AnyCancellable>()
#Published private var specificStore: LibraryStore
var isBookVeryExpensive: Bool { ... }
func bookMysteriouslyDisappears() {
specificStore.removeBook()
}
}
Thanks #NewDev for pointing out subclassing as a smarter route.
If you want to nest ObservableObjects or have an ObservableObject re-publish changes in objects within an object passed to it, this approach works with less code than in my question.
In searching to simplify further with a property wrapper (to get at parent objectWillChange and simplify this further), I noticed a similar approach in this thread: https://stackoverflow.com/a/58406402/11420986. This only differs in using a variadic parameter.
Define VM and Store/Repo Classes
import Foundation
import Combine
class Repo: ObservableObject {
func publish() {
objectWillChange.send()
}
}
class VM: ObservableObject {
private var repoSubscriptions = Set<AnyCancellable>()
init(subscribe repos: Repo...) {
repos.forEach { repo in
repo.objectWillChange
.receive(on: DispatchQueue.main) // Optional
.sink(receiveValue: { [weak self] _ in
self?.objectWillChange.send()
})
.store(in: &repoSubscriptions)
}
}
}
Example Implementation
Repo: add didSet { publish() } to model objects
VM: The super.init() accepts any number of repos to republish
import Foundation
class UserDirectoriesRepo: Repo, DirectoriesRepository {
init(persistence: Persistence) {
self.userDirs = persistence.loadDirectories()
self.persistence = persistence
super.init()
restoreBookmarksAccess()
}
private var userDirs: UserDirectories {
didSet { publish() }
}
var someExposedSliceOfTheModel: [RootDirectory] {
userDirs.rootDirectories.filter { $0.restoredURL != nil }
}
...
}
import Foundation
class FileStructureVM: VM {
init(directoriesRepo: DirectoriesRepository) {
self.repo = directoriesRepo
super.init(subscribe: directoriesRepo)
}
#Published // No longer necessary
private var repo: DirectoriesRepository
var rootDirectories: [RootDirectory] {
repo.rootDirectories.sorted ...
}
...
}
It seems that what you want is a type that notifies when its internal properties change. That sounds an awful lot like what ObservableObject does.
So, make your Store protocol inherit from ObservableObject:
protocol Store: ObservableObject {}
Then a type conforming to Store could decide what properties it wants to notify on, for example, with #Published:
class StringStore: Store {
#Published var text: String = ""
}
Second, you want your view models to automatically fire off their objectWillChange publishers when their store notifies them.
The automatic part can be done with a base class - not with a protocol - because it needs to store the subscription. You can keep the protocol requirement, if you need to:
protocol VM {
associatedtype S: Store
var store: S { get }
}
class BaseVM<S: Store>: ObservableObject, VM {
var c : AnyCancellable? = nil
let store: S
init(store: S) {
self.store = store
c = self.store.objectWillChange.sink { [weak self] _ in
self?.objectWillChange.send()
}
}
}
class MainVM: BaseVM<StringStore> {
// ...
}
Here's an example of how this could be used:
let stringStore = StringStore();
let mainVm = MainVM(store: stringStore)
// this is conceptually what #ObservedObject does
let c = mainVm.objectWillChange.sink {
print("change!") // this will fire after next line
}
stringStore.text = "new text"
I'm developing a simple SwiftUI app in Xcode 11. I want to have a form that loops through multiple user input strings and displays a form with a button. When the user presses the button it modifies the input value - specifically increment or decrement it.
However when passing an array of references like UserInput().foo where UserInput is a published observable object I cannot modify the value inside a ForEach because the ForEach is passed a copy as oppose to the original reference (at least that's my basic understanding). How do I then try to achieve it? I read about inout and everybody says to avoid it but surely this must be a relatively common issue.
I've made an simple example of what I'm trying to do but I can't quite work it out:
import SwiftUI
class UserInput: ObservableObject {
#Published var foo: String = ""
#Published var bar: String = ""
}
struct ContentView: View {
#ObservedObject var input = UserInput()
var body: some View {
LoopInputs()
}
func LoopInputs() -> AnyView?{
var userinputs = [
[UserInput().foo, "Foo"],
[UserInput().bar, "Bar"]
]
var inputs: some View{
VStack(){
ForEach(userinputs, id: \.self){userinput in
Text("\(userinput[1]): \(String(userinput[0]))")
Button(action: {
increment(input: String(userinput[0]))
}){
Text("Increase")
}
}
}
}
return AnyView(inputs)
}
func increment(input: String){
var lead = Int(input) ?? 0
lead += 1
// input = String(lead)
}
}
As I understood, when adding a value to userinputs, the ForEach values doesn't change.
Well, if that's the case, first of all, you could try creating a struct and in it, you declare foo and bar, then just declare a variable of type the struct. It'll look like this:
struct Input: Identifiable {
var id = UUID()
var foo: String
var bar: String
}
class UserInput: ObservableObject {
#Published var inputs: [Input] = [Input]()
}
//ContentView
struct ContentView: View {
#ObservedObject var input = UserInput()
var body: some View {
LoopInputs()
}
func LoopInputs() -> AnyView? {
var inputs: some View {
VStack {
ForEach(input.inputs) { userinput in
Text("\(userinput.bar): \(String(userinput.foo))")
Button(action: {
increment(input: String(userinput.foo))
}) {
Text("Increase")
}
}
}
}
return AnyView(inputs)
}
func increment(input: String) {
var lead = Int(input) ?? 0
lead += 1
// input = String(lead)
}
}
Wouldn't this be easier and more elegant?