I am new to SwiftUI.... View is a protocol which only contains required stored property called "body". My question is , where modifier methods come from. 'cause "Protocol methods must not have bodies".?
My question is , where modifier methods come from. 'cause "Protocol methods must not have bodies".?
From extension like in below example:
extension View {
#ViewBuilder
public func isHidden(_ hidden: Bool) -> some View {
if hidden {
self.hidden()
}
else {
self
}
}
}
Related
I want my users to be able to define whether they want .iconOnly or .titleAndIcon behaviour for their label.
Unexpectedly to me though I am not able to apply on or the other style conditionally at the top of my view hierarchy:
Any suggestions on what needs to be done here?
Here is a possible approach. Tested with Xcode 13.4 / iOS 15.5
extension View {
#ViewBuilder
func labelStyle(includingText: Bool) -> some View {
if includingText {
self.labelStyle(.titleAndIcon)
} else {
self.labelStyle(.iconOnly)
}
}
}
and usage like
TabView {
// .. your code
}
.labelStyle(includingText: labelStyleShowText)
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 }
}
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.
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.