In SwiftUI, Why there is no `body` method for View [duplicate] - swiftui

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.

Related

Understanding SwiftUI syntax: What is actually defined inside var body?

Starting to learn SwiftUI I am a bit confused on how Viewss are implemented. View is actually not a type but a protocol which requires a var body of type View.
Problem 1: So the protocol requires it self. Is this not an infinite, recursive loop? Implementation of View requires a var body that implements View which requires a var body... How does this work?
Problem 2: var body is usually implemented as computed property. This is nothing unusual. However, the implementation does not return anything but only "creates" subviews which are not explicitly added to their parent view. Initializing the sub views is enough.
This is not only the case with the body var but with all other views which include other views like HStack, VStack, etc.
struct SomeView: View {
var body: some View {
ViewA()
OtherView()
VStack {
Sub1()
Sub2()
}
}
}
How can this work? Is this valid Swift syntax? I mean a "normal" computed property would look like this, wouldn't it?
var someValue: Int {
let value1 = getValue()
let value2 = SomeOtherValue()
return value1 + value2
}
And not like this:
var someValue: Int {
getValue()
SomeOtherValue()
}

Passing #Published property where #Binding is expected?

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 }
}

Generic Struct 'ObservedObject' Requires That Conform To 'ObservableObject

I am having trouble with understanding why the code below will not compile. I'm getting an error stating that I must conform to ObservableObject and I cannot see why I wouldn't be.
I've simplified to show that I am seeing. I have two classes. The second observes the first and then the view observes the second.
First Class
import Foundation
import SwiftUI
import CoreBluetooth
class BLEPeripheralDevice: NSObject, ObservableObject {
#Published var bodySesnorLocation: String = ""
}
Second Class
import Foundation
import SwiftUI
import CoreBluetooth
class BLEManager: NSObject, ObservableObject {
#ObservedObject var blePeripheralDevice: BLEPeripheralDevice!
#Published var blePeripheralName: String = ""
}
View
import SwiftUI
struct BluetoothDeviceView: View {
#ObservedObject var bleManager = BLEManager()
var body: some View {
VStack (spacing: 10) {
Text("Bluetooth Devices")
}
}
When I compile this code I am getting an error in the second class on the following line.
#ObservedObject var blePeripheralDevice: BLEPeripheralDevice!
Generic struct 'ObservedObject' requires that 'BLEPeripheralDevice?'
conform to 'ObservableObject'
I don't understand why this would be. Any help is appreciated.
ObservedObject is a property wrapper mainly for Views. Use Published instead..
#Published var blePeripheralDevice: BLEPeripheralDevice!

How to initialize an ObservedObject from a view created with a NavigationLink?

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()
}

In SwiftUI, what's the difference between .modifier and .layout

I cannot seem to find any difference between applying a ViewModifier using either .modifier or .layout. They both produce the same result. Anyone knows what's the difference between these two. There's no documentation whatsoever.
For example, given this modifier:
struct RedTitle: ViewModifier {
func body(content: Content) -> some View {
return content.foregroundColor(.red).font(.title)
}
}
These two views turn out to look identical:
Text("Hello world!").layout(RedTitle())
Text("Hello world!").modifier(RedTitle())
UPDATE
As of Xcode 11 beta 4, the layout modifier has been marked deprecated:
extension View {
#available(*, deprecated, renamed: "modifier")
#inlinable public func layout<T>(_ layout: T) -> some SwiftUI.View where T : SwiftUI.ViewModifier {
return modifier(layout)
}
}
ORIGINAL
There is no difference as of Xcode 11 beta 2. That doesn't mean there will always be no difference. Possibly layout is left over from an older design and needs to be removed, or perhaps a later beta will make it behave differently.
The complete interface exported by SwiftUI can be found in this file:
/Applications/Xcode-beta.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/SwiftUI.framework/Modules/SwiftUI.swiftmodule/arm64.swiftinterface
Looking in that file, you can find the declaration of func modifier:
extension View {
public typealias Modified<T> = _ModifiedContent<Self, T> where T : SwiftUI.ViewModifier
#inlinable public func modifier<T>(_ modifier: T) -> Modified<T> where T : SwiftUI.ViewModifier {
return .init(content: self, modifier: modifier)
}
}
And the declaration of func layout:
extension View {
#inlinable public func layout<T>(_ layout: T) -> Modified<T> where T : SwiftUI.ViewModifier {
return modifier(layout)
}
}
Because both modifier and layout are declared #inlinable, Swift includes the function bodies in the .swiftinterface file. We can see that layout just calls modifier and does nothing else.