How can I access modifiers of a view in SwiftUI?
The return type of myImage
is:
_ModifiedContent<Image, _ShadowEffect>
We can access the original image by doing:
myImage.content
We can access the shadow effect modifier by typing:
myImage.modifier
So to do what you want, you have to type:
print(myImage.modifier.radius)
In SwiftUI, How to access the view that is calling onDisappear() modifier in the closure of it?
Since your HostView
is a simple struct you can just hold the EditView
as a var
inside your HostView
like this:
struct HostView: View {
var editView = EditView()
var body: some View {
editView
.onDisappear {
print(editView.property1)
}
}
}
Modify view from within a view modifier
Here is possible solution:
if to you have declaration like
struct HighlightedText: View {
var defaultFount = Font.body
var highlightedFont = Font.headline
// ... other code
then your custom modifiers could be as
extension HighlightedText {
func defaultFont(_ font: Font) -> Self {
var view = self
view.defaultFount = font
return view
}
func highlightedFont(_ font: Font) -> Self {
var view = self
view.highlightedFont = font
return view
}
}
How to access to the children views in SwiftUI?
This is what you're looking for, if I'm understanding your question correctly:
struct FullButton<Label>: View where Label: View {
var action: () -> Void
var label: () -> Label
var body: some View {
Button(action: self.action, label: self.label)
}
}
This would allow you to pass whatever content you want to be displayed on your button, meaning that the code you have here would now work:
FullButton(action: {
print("touched")
}) {
Text("Button")
}
Update
After looking over your question several times, I've realized that your confusion is stemming from a misunderstanding of what is happening when you create a normal Button
.
In the code below, I'm creating a Button
. The button takes two arguments - action
and label
.
Button(action: {}, label: {
Text("Button")
})
If we look at the documentation for Button
, we see that it is declared like this:
struct Button<Label> where Label : View
If we then look at the initializers, we see this:
init(action: @escaping () -> Void, @ViewBuilder label: () -> Label)
Both action
and label
expect closures. action
expects a closure with a return type of Void
, and label
expects a @ViewBuilder
closure with a return type of Label
. As defined in the declaration for Button
, Label
is a generic representing a View
, so really, label
is expecting a closure that returns a View
.
This is not unique to Button
. Take HStack
, for example:
struct HStack<Content> where Content : View
init(alignment: VerticalAlignment = .center, spacing: Length? = nil, @ViewBuilder content: () -> Content)
Content
serves the same purpose here that Label
does in Button
.
Something else to note - when we create a button like this...
Button(action: {}) {
Text("Button")
}
...we're actually doing the same thing as this:
Button(action: {}, label: {
Text("Button")
})
In Swift, when the last argument in a method call is a closure, we can omit the argument label and append the closure to the outside of the closing parenthesis.
In SwiftUI, you cannot implicitly pass content to any View
. The View
must explicitly accept a @ViewBuilder
closure in its initializer.
And so, you cannot pass a @ViewBuilder
closure to FullButton
unless FullButton
accepts a @ViewBuilder
closure as an argument in its initializer, as shown at the beginning of my answer.
Order of modifiers in SwiftUI view impacts view appearance
Wall of text incoming
It is better not to think of the modifiers as modifying the MapView
. Instead, think of MapView().edgesIgnoringSafeArea(.top)
as returning a SafeAreaIgnoringView
whose body
is the MapView
, and which lays out its body differently depending on whether its own top edge is at the top edge of the safe area. You should think of it that way because that is what it actually does.
How can you be sure I'm telling the truth? Drop this code into your application(_:didFinishLaunchingWithOptions:)
method:
let mapView = MapView()
let safeAreaIgnoringView = mapView.edgesIgnoringSafeArea(.top)
let framedView = safeAreaIgnoringView.frame(height: 300)
print("framedView = \(framedView)")
Now option-click mapView
to see its inferred type, which is plain MapView
.
Next, option-click safeAreaIgnoringView
to see its inferred type. Its type is _ModifiedContent<MapView, _SafeAreaIgnoringLayout>
. _ModifiedContent
is an implementation detail of SwiftUI and it conforms to View
when its first generic parameter (named Content
) conforms to View
. In this case, its Content
is MapView
, so this _ModifiedContent
is also a View
.
Next, option-click framedView
to see its inferred type. Its type is _ModifiedContent<_ModifiedContent<MapView, _SafeAreaIgnoringLayout>, _FrameLayout>
.
So you can see that, at the type level, framedView
is a view whose content has the type of safeAreaIgnoringView
, and safeAreaIgnoringView
is a view whose content has the type of mapView
.
But those are just types, and the nested structure of the types might not be represented at run time in the actual data, right? Run the app (on a simulator or a device) and look at the output of the print statement:
framedView =
_ModifiedContent<
_ModifiedContent<
MapView,
_SafeAreaIgnoringLayout
>,
_FrameLayout
>(
content:
SwiftUI._ModifiedContent<
Landmarks.MapView,
SwiftUI._SafeAreaIgnoringLayout
>(
content: Landmarks.MapView(),
modifier: SwiftUI._SafeAreaIgnoringLayout(
edges: SwiftUI.Edge.Set(rawValue: 1)
)
),
modifier:
SwiftUI._FrameLayout(
width: nil,
height: Optional(300.0),
alignment: SwiftUI.Alignment(
horizontal: SwiftUI.HorizontalAlignment(
key: SwiftUI.AlignmentKey(bits: 4484726064)
),
vertical: SwiftUI.VerticalAlignment(
key: SwiftUI.AlignmentKey(bits: 4484726041)
)
)
)
)
I've reformatted the output because Swift prints it on a single line, which makes it very hard to understand.
Anyway, we can see that in fact framedView
apparently has a content
property whose value is the type of safeAreaIgnoringView
, and that object has its own content
property whose value is a MapView
.
So, when you apply a “modifier” to a View
, you're not really modifying the view. You're creating a new View
whose body
/content
is the original View
.
Now that we understand what modifiers do (they construct wrapper View
s), we can make a reasonable guess about how these two modifiers (edgesIgnoringSafeAreas
and frame
) affect layout.
At some point, SwiftUI traverses the tree to compute each view's frame. It starts with the screen's safe area as the frame of our top-level ContentView
. It then visits the ContentView
's body, which is (in the first tutorial) a VStack
. For a VStack
, SwiftUI divides up the frame of the VStack
among the stack's children, which are three _ModifiedContent
s followed by a Spacer
. SwiftUI looks through the children to figure out how much space to allot to each. The first _ModifiedChild
(which ultimately contains the MapView
) has a _FrameLayout
modifier whose height
is 300 points, so that's how much of the VStack
's height gets assigned to the first _ModifiedChild
.
Eventually SwiftUI figures out which part of the VStack
's frame to assign to each of the children. Then it visits each of the children to assign their frames and lay out the children's children. So it visits that _ModifiedContent
with the _FrameLayout
modifier, setting its frame to a rectangle that meets the top edge of the safe area and has a height of 300 points.
Since the view is a _ModifiedContent
with a _FrameLayout
modifier whose height
is 300, SwiftUI checks that the assigned height is acceptable to the modifier. It is, so SwiftUI doesn't have to change the frame further.
Then it visits the child of that _ModifiedContent
, arriving at the _ModifiedContent
whose modifier is `_SafeAreaIgnoringLayout. It sets the frame of the safe-area-ignoring view to the same frame as the parent (frame-setting) view.
Next SwiftUI needs to compute the frame of the safe-area-ignoring view's child (the MapView
). By default, the child gets the same frame as the parent. But since this parent is a _ModifiedContent
whose modifier is _SafeAreaIgnoringLayout
, SwiftUI knows it might need to adjust the child's frame. Since the modifier's edges
is set to .top
, SwiftUI compares the top edge of the parent's frame to the top edge of the safe area. In this case, they coincide, so Swift expands the frame of the child to cover the extent of the screen above the top of the safe area. Thus the child's frame extends outside of the parent's frame.
Then SwiftUI visits the MapView
and assigns it the frame computed above, which extends beyond the safe area to the edge of the screen. Thus the MapView
's height is 300 plus the extent beyond the top edge of the safe area.
Let's check this by drawing a red border around the safe-area-ignoring view, and a blue border around the frame-setting view:
MapView()
.edgesIgnoringSafeArea(.top)
.border(Color.red, width: 2)
.frame(height: 300)
.border(Color.blue, width: 1)
The screen shot reveals that, indeed, the frames of the two _ModifiedContent
views coincide and don't extend outside the safe area. (You might need to zoom in on the content to see both borders.)
That's how SwiftUI works with the code in the tutorial project. Now what if we swap the modifiers on the MapView
around as you proposed?
When SwiftUI visits the VStack
child of the ContentView
, it needs to divvy up the VStack
's vertical extent amongst the stack's children, just like in the prior example.
This time, the first _ModifiedContent
is the one with the _SafeAreaIgnoringLayout
modifier. SwiftUI sees that it doesn't have a specific height, so it looks to the _ModifiedContent
's child, which is now the _ModifiedContent
with the _FrameLayout
modifier. This view has a fixed height of 300 points, so SwiftUI now knows that the safe-area-ignoring _ModifiedContent
should be 300 points high. So SwiftUI grants the top 300 points of the VStack
's extent to the stack's first child (the safe-area-ignoring _ModifiedContent
).
Later, SwiftUI visits that first child to assign its actual frame and lay out its children. So SwiftUI sets the safe-area-ignoring _ModifiedContent
's frame to exactly the top 300 points of the safe area.
Next SwiftUI needs to compute the frame of the safe-area-ignoring _ModifiedContent
's child, which is the frame-setting _ModifiedContent
. Normally the child gets the same frame as the parent. But since the parent is a _ModifiedContent
with a modifier of _SafeAreaIgnoringLayout
whose edges
is .top
, SwiftUI compares the top edge of the parent's frame to the top edge of the safe area. In this example, they coincide, so SwiftUI extends the frame of the child to the top edge of the screen. The frame is thus 300 points plus the extent above the top of the safe area.
When SwiftUI goes to set the frame of the child, it sees that the child is a _ModifiedContent
with a modifier of _FrameLayout
whose height
is 300. Since the frame is more than 300 points high, it isn't compatible with the modifier, so SwiftUI is forced to adjust the frame. It changes the frame height to 300, but it does not end up with the same frame as the parent. The extra extent (outside the safe area) was added to the top of the frame, but changing the frame's height modifies the bottom edge of the frame.
So the final effect is that the frame is moved, rather than expanded, by the extent above the safe area. The frame-setting _ModifiedContent
gets a frame that covers the top 300 points of the screen, rather than the top 300 points of the safe area.
SwiftUI then visits the child of the frame-setting view, which is the MapView
, and gives it the same frame.
We can check this using the same border-drawing technique:
if false {
// Original tutorial modifier order
MapView()
.edgesIgnoringSafeArea(.top)
.border(Color.red, width: 2)
.frame(height: 300)
.border(Color.blue, width: 1)
} else {
// LinusGeffarth's reversed modifier order
MapView()
.frame(height: 300)
.border(Color.red, width: 2)
.edgesIgnoringSafeArea(.top)
.border(Color.blue, width: 1)
}
Here we can see that the safe-area-ignoring _ModifiedContent
(with the blue border this time) has the same frame as in the original code: it starts at the top of the safe area. But we can also see that now the frame of the frame-setting _ModifiedContent
(with the red border this time) starts at the top edge of the screen, not the top edge of the safe area, and the bottom edge of the frame has also been shifted up by the same extent.
Related Topics
Dyld: Library Not Loaded, App Requires Afnetworking 2.0.0 But Provides Version 1.0.0
How to Get Title from Wkinterfacebutton
How to Call a Method on a Uiview from Outside the Uiviewrepresentable in Swiftui
New' Is Unavailable: You Cannot Directly Instantiate an Stpissuingcardpin
Fail to Import Restkit with Cocoapods Dynamic Frameworks
How to Declare Exponent/Power Operator with New Precedencegroup in Swift 3
Swift. Combine. How to Call a Publisher Block More Than Once When Retry
Swift Programmatically Create Function for Button with a Closure
Parse.Com Pfgeopoint.Geopointforcurrentlocationinbackground Not Doing Anything
Swift Package Manager Unable to Compile Ncurses Installed Through Homebrew
Recursion Over a Swift Sliceable
How Does Appdelegate.Swift Replace Appdelegate.H and Appdelegate.M in Xcode 6.3
Swift Seems to Be Slower as Objective-C in Loops
Swift 3 - How to Write Functions with No Initialisers Like the New Uicolors
Round Currency Closest to Five
How to Use Bit Field with Swift to Store Values with More Than 1 Bit
How to Extend Float3 or Any Other Built-In Type to Conform to the Codable Protocol