Why primitive View in SwiftUI has no body? - ios

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

Alternate of ViewController in SwiftUI project

What is the counterpart of ViewController class in a project with interface of type SwiftUI?
I am following this documentation to initialize adLoader from Google AdMob in a SwiftUI project. All examples in the document are within a ViewController : UIViewController class. However, I saw that ViewController is only available when I create a project with storyboard as the interface. But not SwiftUI. Examples in this documentation are initializing the adLoader in the following way by assignment using the keyword self, to parameters rootViewController and adLoader.delegate. My project was created with interface as SwiftUI and does not have a ViewController class. I have a main app class that implements App protocol with #main annotation. I then have a view that is being loaded from the main app class. Please help me understand what I can use in place of the keyword self to initialize the adLoader object.
Code from the google document I am trying to work with:
var adLoader: GADAdLoader!
var nativeAdView: GADNativeAdView!
adLoader = GADAdLoader(adUnitID: YOUR_AD_UNIT_ID, rootViewController: self,
adTypes: [.native],
options: [multipleAdsOptions])
adLoader.delegate = self
Documentation for GADAdLoader
Sample code in project
MyApp.swift
#main
struct MyApp: App {
init() {}
var body: some Scene {
WindowGroup{
HomeView()
}
}
HomeView.swift
struct HomeView: View {
var body: some View {
customMainNavBar
.onAppear(){
------
------
}
}
}
Errors I get when I put this code in the View or the main App.
Cannot convert value of type 'HomeView' to expected argument type 'UIViewController?'
Cannot assign value of type 'HomeView' to type 'GADAdLoaderDelegate?'
Cannot convert value of type 'MyApp' to expected argument type 'UIViewController?'
Cannot assign value of type 'MyApp' to type 'GADAdLoaderDelegate?'

Cannot assign value of type 'V' to type some 'Protocol'

I have a protocol:
import SwiftUI
...
protocol MyProtocol : View
{
var aValue: CGFloat { get }
}
Then I have a property in a UIViewController:
var contentView: some MyProtocol = MyView()
Where MyView is:
struct MyView : MyProtocol
{
var aValue: CGFloat = 0.25
var body: some View
{
...
}
}
Back in my view controller I have:
func showView<V: MyProtocol>(view: V)
{
...
contentView = view // ERROR Happens here.
}
Cannot assign value of type 'V' to type 'some MyProtocol'.
Why do I get this error and how can it be avoided?
var contentView: some MyProtocol = MyView()
So the type of contentView is "some specific, secret (opaque) type, unknown to anything but the compiler, that conforms to MyProtocol, and also happens to be exactly MyView, even though nothing can know that." It's not "something that conforms to MyProtocol" which it seems maybe you're thinking it is. If you mean that, the syntax is:
var contentView: MyProtocol = MyView()
The point of some is that the type is statically known at compile-time by the compiler, but not known to the caller, or by anything else.
For example, even this would fail:
var contentView: some MyProtocol = MyView()
contentView = MyView() // Cannot assign value of type 'MyView' to type 'some MyProtocol'
The compiler will not prove that MyView is exactly the secret type that contentView used. (For most errors of this type I'd say the compiler "cannot prove," but in this case, it's an active decision to forbid proving the fact because that's what some does.)
That "secret" type is carried along, however, and is well defined, it's just opaque. For example, the following is fine:
var contentView: some MyProtocol = MyView()
let otherView = contentView // otherView is definitely the same as as contentView
contentView = otherView // so it can be assigned
At first pass, I expect the code you want is just the above var contentView: MyProtocol, but it's very possible you have a deeper misunderstanding about SwiftUI. You cannot just swap in arbitrary Views in SwiftUI. As a rule, everything should be decided at compile-time, not runtime. There are tools like AnyView to work around this, but generally should not be your first choice. I expect there's a deeper design problem here that isn't in your question.
For more details of opaque types, see SE-244.
See Rob's answer for a good explanation of why, currently, your view controller is generic as follows and you haven't realized it.
final class ViewController<View: MyProtocol> {
private(set) var contentView: View
init(contentView: View) {
self.contentView = contentView
}
func showView(view: View) {
contentView = view
}
}
The property initializer you're using only applies to one of the potentially infinite ViewControllers that may be.
extension ViewController where View == MyView {
convenience init() {
self.init(contentView: .init())
}
}

SwiftUI - Is this code instantiating a new Scene object?

I'm a Java developer trying to learn Swift/SwiftUI. I am going through the Apple tutorial for SwiftUI (https://developer.apple.com/tutorials/swiftui/creating-and-combining-views) In it they have the following code snippet:
import SwiftUI
#main
struct LandmarksApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
I am trying to understand this portion:
var body: some Scene {
WindowGroup {
ContentView()
}
}
I understand that it creates a new variable called body of type Scene. I know that you can instantiate objects like this:
var x: String = "test"
but I'm not familiar with the syntax. Does that create a new object of type Scene?
I have gone through this
but I couldn't find this syntax on there.
This syntax is indeed hard nut to crack for Swift novices. It involves implicit getters, constructors, several kind of closure shortcuts, implicit return types, generic type inference and opaque types. Let's go step by step.
Getter syntax for computed properties
LandmarksApp conforms to the App protocol. This requires the definition of a body property of a type complying with the Scene protocol. Moreover, we are told in the documentation that the body is expected to be a read-only computed property.
The general syntax of a computed property's getter is:
var X : T {
get { ... }
...
}
But if a it's a read-only propery, a shortcut may be used: put the getter code in a block directly after the type and without the get:
var CX : T { ... } // that's a computed property with its getter
Moreover, you do not need a return if the getter code is made of a single expression: the return is implicit.
WindowGroup constructor using a closure
WindowsGroup is the type we are going to use. It complies with Scene. It is a generic type based on the type parameter Content that conforms to the View protocol, and we have a ContentView for that. So here the first try:
struct LandmarksApp: App {
var body : WindowGroup<ContentView> { // read-only getter
WindowGroup<ContentView>(content: myhelper) // return a newly constructed object
}
}
The constructor for WindowGroup<T> requires as argument a function that takes no argument and returns a T. To start simple, an ordinary function was used. It's defined as follows:
func myhelper() -> ContentView {
return ContentView()
}
Simplification based on closures and generics
We can simplify with a closure to replace the helper function:
var body : WindowGroup<ContentView> {
WindowGroup<ContentView>(content: { ()->ContentView in return ContentView() })
}
Swift can infer the types used in the closure from the context and it also allows an implicit return for closures made of a single expression. In absence of parameters, we remove in. All those simplification lead to:
var body : WindowGroup<ContentView> {
WindowGroup<ContentView>(content: { ContentView() })
}
Swift is also able to infer the generic type from the context. So this expression could be simplified to:
var body : WindowGroup<ContentView> {
WindowGroup (content: { ContentView() })
}
Last but not least, the most tricky part: Swift allows a special syntax if the closure is the last argument of a function. It's called trailing closures. This leads to:
var body : WindowGroup<ContentView> {
WindowGroup () { ContentView() }
}
This syntax also allows to get rid of the empty parenthesis:
var body : WindowGroup<ContentView> {
WindowGroup { ContentView() }
}
Opaque types
Until now, we used a body of a concrete well-known type. But couldn't the type of this variable be inferred? It would thus be tempting to write:
var body { // OUCH !!!
WindowGroup { ContentView() }
}
But Swift requires computed properties to have an explicit type. To keep it as general as possible but ensure compliance with the required protocol, an opaque type may be used:
var body : some Scene {
WindowGroup { ContentView() }
}
More on opaque types in this question.
Wrap up

Using Kotlin mulitplatform classes in SwiftUI

I am building a small project using the Kotlin Mulitplatform Mobile (KMM) framework and am using SwiftUI for the iOS application part of the project (both of which I have never used before).
In the boilerplate for the KMM application there is a Greeting class which has a greeting method that returns a string:
package com.example.myfirstapp.shared
class Greeting {
fun greeting(): String {
return "Hello World!"
}
}
If the shared package is imported into the iOS project using SwiftUI, then the greeting method can be invoked and the string that's returned can put into the View (e.g. Text(Greeting().greeting()))
I have implemented a different shared Kotlin class whose properties are modified/fetched using getters and setters, e.g. for simplicity:
package com.example.myfirstapp.shared
class Counter {
private var count: Int = 0
getCount() {
return count
}
increment() {
count++
}
}
In my Android app I can just instantiate the class and call the setters to mutate the properties of the class and use it as application state. However, I have tried a number of different ways but cannot seem to find the correct way to do this within SwiftUI.
I am able to create the class by either creating a piece of state within the View that I want to use the Counter class in:
#State counter: Counter = shared.Counter()
If I do this then using the getCount() method I can see the initial count property of the class (0), but I am not able to use the setter increment() to modify the property the same way that I can in the Android Activity.
Any advice on the correct/best way to do this would be greatly appreciated!
Here's an example of what I'd like to be able to do just in case that helps:
import shared
import SwiftUI
struct CounterView: View {
#State var counter: shared.Counter = shared.Counter() // Maybe should be #StateObject?
var body: some View {
VStack {
Text("Count: \(counter.getCount())")
Button("Increment") { // Pressing this button updates the
counter.increment() // UI state on the previous line
}
}
}
}
I believe the fundamental issue is that there isn't anything that's notifying SwiftUI layer when the count property is changed in the shared code (when increment is called). You can at least verify that value is being incremented by doing something like following (where we manually retrieve updated count after incrementing it)
struct ContentView: View {
#ObservedObject var viewModel = ViewModel(counter: Counter())
var body: some View {
VStack {
Text("Count: \(viewModel.count)")
Button("Increment") {
viewModel.increment()
}
}
}
}
class ViewModel: ObservableObject {
#Published var count: Int32 = 0
func increment() {
counter.increment()
count = counter.getCount()
}
private let counter: Counter
init(counter: Counter) {
self.counter = counter
}
}

Error: Use of undeclared type BindableObject

I’m following this tutorial for SwiftUI amplify app where I came across this error when creating a final class which conforms to Bindable object.
Error:Use of undeclared type 'BindableObject'
import Combine
import SwiftUI
import AWSAppSync
final class TalkStore: BindableObject {
/*
Required by SwiftUI
*/
let didChange = PassthroughSubject<TalkStore, Never>()
var listTalks: [ListTodosQuery.Data.ListTodo.Item] {
didSet {
didChange.send(self)
}
}
//We will be using this later.
private let appSyncClient: AWSAppSyncClient!
/*
Init if running app is using SwiftUI Content View
*/
init(talks: [ListTodosQuery.Data.ListTodo.Item]) {
self.appSyncClient = nil
self.listTalks = talks
}
}
Is it possible that Apple has changed the class name?
How do I find that out?
BindableObject has been renamed ObservableObject
BindableObject is replaced by the ObservableObject protocol from the Combine framework. (50800624)
Source: https://developer.apple.com/documentation/ios_ipados_release_notes/ios_13_release_notes

Resources