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

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.

Related

Applying different LabelStyle conditionally fails

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)

How do I cast ViewModifier Content to anticipated type?

So I think I could be using ViewModifiers incorrectly, but is it possible to cast the Content of a ViewModifier to the anticipated type, e.g. Text? I want to use .fontWeight(.bold) but that is not part of the base/aliastype Content. Alternatively what should I use to achieve this effect? A custom Style function?
struct ButtonTextModifier: ViewModifier {
func body(content: Content) -> some View {
(content as Text) //<--- ERROR HERE
.fontWeight(.bold) //<--- Because I want to use this
.font(.largeTitle)
.padding()
}
}
The error I get:
Cannot convert value of type 'ButtonTextModifier.Content' (aka '_ViewModifier_Content<ButtonTextModifier>') to type 'Text' in coercion
You might have to add a new method on Text by declaring it in it's extension.
extension Text {
func buttonTextModifier() -> some View {
self
.fontWeight(.bold)
.font(.largeTitle)
.padding()
}
}

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

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.

View protocol in SwiftUI

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

Laying out Rectangle() Views in SwiftUI inside of ForEach?

So, I am trying to layout Rectangle shapes on a SwiftUI View, like this:
I am not sure how to best go about this. Any suggestion?
EDIT:
I added this and it now works. Is there a better more proper solution?
extension CGRect: Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(NSCoder.string(for: self).hashValue)
}
}
The error saying you the reason. I recommend you to create a class. So you will able to make it BindableObject if needed and add more properties like Color if needed
class RectangleModel: NSObject, Identifiable {
var rect: CGRect
init(rect: CGRect) {
self.rect = rect
}
}
Or the best is to create one more model(like RectangleStore) that will be a BindableObject and will contain array of RectangleModels. It will be much flexible