When some calculation happens in viewModel I want to present modal view. Normally I need to set some boolean binding for method:
.fullScreenCover(isPresented: $isGalleryPresented) {
GalleryPickerView())
}
where isGalleryPresented, is #State definied in view. However browsing SO, i have found out that I could have property in viewModel:
#Published var isGalleryPresented = false
and then do something like this:
.fullScreenCover(isPresented: $viewModel.isGalleryPresented) {
GalleryPickerView()
}
And this works just fine, although I don't know how. fullScreenCover method argument of type isPresented: Binding<Bool>, and I pass as far as I can tell a publisher. How does this work?
Your viewModel is a wrapped property of #ObservedObject, which provides binding via keypath subscript to wrapped observable object properties.
Here is a corresponding part of declaration:
#available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) #propertyWrapper #frozen public
struct ObservedObject<ObjectType> : DynamicProperty where ObjectType : ObservableObject {
/// A wrapper of the underlying observable object that can create bindings to
/// its properties using dynamic member lookup.
#dynamicMemberLookup #frozen public struct Wrapper {
/// Returns a binding to the resulting value of a given key path.
///
/// - Parameter keyPath : A key path to a specific resulting value.
///
/// - Returns: A new binding.
public subscript<Subject>(dynamicMember keyPath: ReferenceWritableKeyPath<ObjectType, Subject>) -> Binding<Subject> { get }
}
Related
struct NotesView: View {
#State var notesArray = [Note]()
public var deleteid: String
var body: some View {
List{
ForEach(notesArray, id: \._id) { notesArray in
NavigationLink(destination: AddNotesView(addNotesViewIdentifier: "updateNote", id: notesArray._id, title: notesArray.title, note: notesArray.note, noteDate: notesArray.date)){
HStack {
Text(notesArray.title)
deleteid = notesArray._id //ERROR - Cannot assign to property: 'self' is immutable
}
}
}
.onDelete(perform: deleteNoteAtIndex)
}
}
func deleteNoteAtIndex(at offsets: IndexSet){ APIFunctions.functions.DeleteNote(id: _id) }
I was expecting the variable "deleteid" to update.
I assumed you can modify any variable by calling that variable and set it equal to a new value.
Like this
First declare variable:
var deleteid: String
next modify variables string valve
deleteid = notesArray._id
A couple of things:
This isn't directly related to your question, but may help you navigation your own code better... When you create a ForEach view to iterate over an array, you should use a different name for the value that represents each element in the iteration. Here, you're using the name notesArray for your array of notes, then creating a second local variable called notesArray for the loop. That variable inside the block will be an instance of Note, so I'd name it note, e.g.:
ForEach(notesArray, id: \._id) { note in
NavigationLink(destination: AddNotesView(addNotesViewIdentifier: note._id, // etc
}
If you want variables to be modifiable inside views, they should be #State variables. This is important due to the way Swift struct lifecycles work, and how the SwiftUI rendering system loads and reloads structs as it works out what has changed.
I'm not entirely sure what deleteid is supposed to represent here, and it's possible you don't need it at all. If you're using the onDelete modifier to implement SwiftUI's native swipe-to-delete system, SwiftUI will give you an IndexSet, which is a collection (usually of just one) of the positions of the item(s) to delete in the array.
From there, you can find the item(s) at each index and then either remove them, or lookup some other value (e.g., their _id attribute) and do some other operation on them.
So the method you might call in onDelete could look something like:
func deleteNoteAtIndex(offsets: IndexSet) {
// get the array objects that the offsets point to
let notes = offsets.map { noteArray[$0] }
for note in notes {
APIFunctions.functions.deleteNote(id: note._id)
}
}
This gives me the following error:
Cannot force unwrap value of non-optional type 'Binding<Person?>
I don't understand why I can't force unwrap a binding?
struct Person {
var name: String
}
struct ContentView: View {
#State private var person: Person? = Person(name: "Peter")
var body: some View {
if person != nil {
TextField("", text: $person!.name)
}
}
}
When you declare a State variable in your view, you'll get a Binding to the value by adding a leading $ to the variable's name.
So, in your code, you'll have a $person that is binding to an optional Person type. $person is Binding<Person?>
To pass value to TextField you'll need a Binding<String>. you can't force-unwrap $person because it's not an optional value. It's a Binding to an optional type. To access the name field inside the Person struct, you'll need a Binding<Person> instead.
Fortunately, there's a method to get what you want.
By using this initializer, you'll have a Binding<Person>?. Note that now instead of a Binding to an optional, you have an optional Binding.
You should be able to use this new binding like this:
// Binding($person) returns Binding<Person>?
TextField("", text: Binding($person)!.name)
Update:
As #Jessy mentioned in the comments, instead of force-unwrapping the optional binding, we can use map to transform the returned Binding to a TextField
var body: some View {
Binding($person).map {
TextField("", text: $0.name)
}
}
As far as I know, the definition of the ViewModifier protocol looks like this:
protocol ViewModifier {
// content view type passed to body()
typealias Content
// type of view returned by body()
associatedtype Body : View
// only requirement
func body(content: Self.Content) -> Self.Body
}
My question is:
Why Self.Content is a typealias while Self.Body is an associatedtype ?
What's the difference?
Why Self.Content is a typealias while Self.Body is an associatedtype ? What's the difference?
Because Content is a typealias, the author of the ViewModifier protocol gets to pick the type being aliased when she writes the protocol. (You can't see the type being aliased because that type is _ViewModifier_Content<Self>. When an identifier in the SDK starts with _, Apple omits the identifier from documentation and generated interfaces.)
Because Body is an associatedtype, you get to pick the type that it aliases, when you write a type that conforms to the ViewModifier protocol. You can make Body be any type you want, subject to two conditions:
You must pick a type that conforms to View (because the ViewModifier protocol constrains Body to conform to View).
You must be able to create or obtain an instance of whatever type you pick, because you have to return an instance of it from the body method. (Or you could crash or hang to avoid returning at all, but that's usually not what you want…)
So, when you implement a type conforming to ViewModifier, you cannot influence what Content means. It always means _ViewModifier_Content<Self>. But you can choose what Body means, by choosing the return type of the body method.
Here's I'll force Body to mean EmptyView:
struct EmptyModifier: ViewModifier {
func body(content: Content) -> EmptyView {
EmptyView()
}
}
And here I'll force Body to mean Color:
struct RedModifier: ViewModifier {
func body(content: Content) -> Color {
Color.red
}
}
Usually we use some View as the type, which means that Swift deduces the exact type for us and keeps it a secret:
struct FrameModifier: ViewModifier {
var color: Color
var width: CGFloat
func body(content: Content) -> some View {
return content
.padding(width)
.border(color, width: width)
}
}
Here, all we know about the Body type is that it conforms to View. Swift tries pretty hard to keep us from finding out the real type at compile time.
typealias only changes the name of the type. Nothing more.
associatedtype is a way to include generics in the protocol implementation.
public protocol ViewModifier {
/// The type of view representing the body of `Self`.
associatedtype Body : View
/// Returns the current body of `self`. `content` is a proxy for
/// the view that will have the modifier represented by `Self`
/// applied to it.
func body(content: Self.Content) -> Self.Body
/// The content view type passed to `body()`.
typealias Content
}
In the above example Body is a concrete type conforming to View and is inferred by the body(content:) return type.
Content is just another name for a type passed as the content parameter.
This answer by rob mayoff explained the ViewModifier in a more detailed way:
So this tells us that, when we write our own ViewModifier, our body
method will receive some sort of View (the specific type is defined by
the framework and we can just call it Content), and return some sort
of View (we get to pick the specific return type).
This means you don't know what the Content is, you just operate on it - it's called Content for your convenience, so you don't have to deal with _ViewModifier_Content<Self>.
The View protocol requires a body property:
public protocol View {
associatedtype Body : View
#ViewBuilder var body: Self.Body { get }
}
Why have some built-in Views in SwiftUI no body?
#frozen public struct EmptyView : View {
#inlinable public init()
public typealias Body = Never
}
#frozen public struct VStack<Content> : View where Content : View {
#inlinable public init(alignment: HorizontalAlignment = .center, spacing: CGFloat? = nil, #ViewBuilder content: () -> Content)
public typealias Body = Never
}
have no body at all..
let emptyView = EmptyView().body
// Value of type 'EmptyView' has no member 'body'
let vStackView = VStack { Text("some text")}.body
// Value of type 'VStack<Text>' has no member 'body'
How are these Views implemented?
Each primitive view does have a body, but it isn't directly accessible at compile-time. The official Swift forums have a thread about this topic. I'll reproduce my analysis here.
Let's consider Text.
A Text value has a body property, as all Views do. Text's body calls fatalError:
import SwiftUI
func body<V: View>(of view: V) -> V.Body { view.body }
body(of: Text("hello"))
// runtime output:
SwiftUI/View.swift:94: Fatal error: body() should not be called on Text.
However, if we attempt to access the body property directly, the compiler rejects the program:
import SwiftUI
Text("hello").body
Output:
The compiler rejects the program because SwiftUI's swiftinterface file doesn't declare a body property for Text.
Why doesn't Text's declaration include body? I don't know for sure because I don't have access to the SwiftUI source code, but I have discovered that we can use #_spi to omit a declaration from a .swiftinterface file.
I put the following into MyView.swift:
import SwiftUI
public struct MyView: View {
#_spi(Private)
public var body: Never { fatalError() }
}
Then I compile it as follows, based on the “Directly invoking the compiler” instructions found here:
swiftc MyView.swift -module-name MyLib -emit-module -emit-library -emit-module-interface -enable-library-evolution
The compiler writes MyLib.swiftinterface as follows, omitting the body declaration:
// swift-interface-format-version: 1.0
// swift-compiler-version: Apple Swift version 5.7 (swiftlang-5.7.0.123.7 clang-1400.0.29.50)
// swift-module-flags: -target arm64-apple-macosx12.0 -enable-objc-interop -enable-library-evolution -module-name MyLib
import Swift
import SwiftUI
import _Concurrency
import _StringProcessing
public struct MyView : SwiftUI.View {
public typealias Body = Swift.Never
}
And it writes MyLib.private.swiftinterface as follows, containing the body declaration:
// swift-interface-format-version: 1.0
// swift-compiler-version: Apple Swift version 5.7 (swiftlang-5.7.0.123.7 clang-1400.0.29.50)
// swift-module-flags: -target arm64-apple-macosx12.0 -enable-objc-interop -enable-library-evolution -module-name MyLib
import Swift
import SwiftUI
import _Concurrency
import _StringProcessing
public struct MyView : SwiftUI.View {
#_spi(Private) #_Concurrency.MainActor(unsafe) public var body: Swift.Never {
get
}
public typealias Body = Swift.Never
}
So my best guess is that SwiftUI applies the #_spi attribute to the body property of each primitive View.
I'm not an expert but this seems very logical. Imagine that no views are offered by SwiftUI and you want to create the very first view. This view has the computed property body that is expecting you to a return a type that conforms to the View protocol (i.e. should have the body property). This will go forever. Hence, there has to be a View without the body property.
My apologies if this is not the right question to ask, as I am completely new to SwiftUI and iOS programming in general. The question indicates what I want to do, and the error I'm getting I believe is a red herring because of the SwiftUI compiler. It's likely that I am taking the incorrect approach to solving this problem altogether.
I am using XCode Version 11.2.1 (11B500)
View utilizing the ObservedObject:
struct Results: View {
var jobId: String
#ObservedObject var jobDetailService: JobDetailService
init(jobId: String) {
self.jobId = jobId
jobDetailService = JobDetailService(jobId: jobId)
}
var body: some View {
//... view code here
}
}
And it is within this view that I am getting the error (at the ZStack line) "Generic parameter 'C0' could not be inferred". When I comment out the NavigationLink block, the error goes away. Further, when the Results view does not depend on the jobId parameter (and we construct JobDetailService inline with #ObservedObject var jobDetailService = JobDetailService(), this all works. However, I need to be able to pass the jobId parameter to the JobDetailService in order to make the network call to fetch and publish the data.
struct JobList: View {
#ObservedObject var jobListService = JobListService()
var body: some View {
NavigationView {
List(jobListService.jobs) {job in
ZStack {
JobCard(name: job.fullName, date: job.lastUpdated)
NavigationLink(destination: Results(jobId: job.jobId)) {
EmptyView()
}
}
}
}
}
}
After reading this article, and thinking about Asperi's advice on not solely relying on initialization, I opted to do the following:
Remove custom initializer from JobDetailService and instead instantiate the service inside my Results view. Then in an .onAppear method on the Results view, call the getJobDetail method from JobDetailService which in turn makes the network call that populates the #ObservedObject. This allows me to pass in the parameters I need and control when the network call is made. Maybe not the right pattern for this problem but it works for my use case for now.
I assume the following should help you:
Declaration...
struct Results: View {
#ObservedObject var jobDetailService: JobDetailService
var body: some View {
//... view code here
}
}
... and usage
NavigationLink(destination: Results(jobDetailService: JobDetailService(jobId: jobId))) {
EmptyView()
}