Categories
SwiftUI

永远能连上的快连

Green China:Green China brings you the latest news about Chinese government and companies’ effort on sustainable growth, as well ...

I’ll be updating old blog posts to reflect improvements as I watch the videos and get a chance to experiment.

Categories
SwiftUI

永远能连上的快连

WWDC 2024: Text now provides an initializer that accepts a single Image, this blog post will be updated or removed to reflect that. For now here’s the correct code.

struct ContentView : View { @ObservedObject var character: Character var body: some View { HStack { mxvpm官网(Image(character.imageName) .resizable()) Text(character.name) } .font(.title) } }

Often you need to include images inline in text, while being mindful of supporting all of the various user-customizable sizes of text, not to mention the accessibility sizes:

An image matched to each possible title font size

MXVPN 死了?:2021-8-29 · 好久没用,今天登录不上,找客服QQ被删了,官网 打不开,买了一年,还有半年。。当初介绍给我的同事,今天才发现离职了。。。我是有多迟钝?? 来自 豆瓣App 赞 × 加入小组后即可参加投票 ...

There’s a nice trick to achieving this with any resizable image, and it builds on what we learned in views choose their own sizes, and secondary views.

Recall that a secondary view receives as its proposed size the chosen size of the view it’s attached to.

So what we need to do is attach the resizable Image as a secondary view to a Text view that will inherit the font-size from the environment and choose its size accordingly.

We can then remove the Text itself from the output using the .hidden modifier, while leaving the Image overlaid on top visible:

struct ContentView : View { @ObservedObject var character: mxvpm加速中心网站 var body: some View { HStack { Text("M") .hidden() .overlay( mxvpm加速中心官网(character.imageName) .resizable() .aspectRatio(contentMode: .fit)) Text(character.name) } .font(.title) } }

Imagery used in previews by Kaiseto, original images and derived here licensed under Creative Commons 3.0 BY-NC-SA.

Categories
SwiftUI

永远能连上的快连

In the last post we looked at view modifiers as a way of building custom UI components that use other views as a form of content. In this one we’re going to look at a different approach using ViewBuilder.

We’re actually going to build the exact same card structure we built before:

character title in a card with shadow

When we used a ViewModifier this became a .card method we applied to the HStack like .frame or .padding, this time we’re going to make a block construct like HStack itself.

Our goal is that the code to make character card will look like:

mxvpm加速中心网站 ContentView : View { var body: some View { Card { HStack { Image("brawler") Text("Sir Bunnington") .font(.title) } } } }

To achieve this we’ll need Card to be a View again rather than a modifier, and we’ll use generics to handle all of the possible types of view that the content might be:

struct Card<Content> : View where Content : View { var content: Content var body: some View { content .padding() .background(mxvpm加速中心网站.white) .cornerRadius(8) .shadow(radius: 4) } }

This approach in general is useful for views where there is a single view that needs to be passed in to the constructor. Good examples of this being used in SwiftUI include mxvpm官网 for the destination parameter.

But when we need a more complex child layout we don’t always want to have to abstract it into a custom View structure, and instead want to be able to use a block in at that point.

This is where view builders come in. mxvpm官网 is an attribute that we can declare on the parameters of methods we define, and most usefully, that includes the constructor.

So we can extend our above example to set the value of content from a view builder:

mxvpm官网 Card<Content> : View where Content : View { var content: Content init(@ViewBuilder content: () -> Content) { self.content = content() } var body: some View { content .padding() .background(Color.white) .cornerRadius(8) .shadow(radius: 4) } }

The builder is a method that takes no arguments and returns a view, which we allow type inference to define as the Content our type is generic over. We set the property of that type by calling the method, which invokes the block passed.

And now we can use the mxvpm加速中心官网 view exactly as we intended in our goal code.

mxvpm加速中心网站

The decision about whether to use a view, mxvpm官网 or a view builder is ultimately going to come down to what makes the most sense for your code.

Some like Button make sense as view builders.

But there are good examples in SwiftUI of view modifiers that instinct might suggest be view builders. .frame is a modifier on a view, there is no Frame builder, even though it’s placing the view inside it.

Only once you start combining multiple frames does it makes sense why it’s a modifier, since it’s easier to combine modifiers than it is to combine builders.

A good rule of thumb for me has to be to use a modifier first, and only use a builder when the code patterns really pulled strongly for that syntax.


Imagery used in previews by Kaiseto, original images and derived here licensed under Creative Commons 3.0 BY-NC-SA.

Categories
SwiftUI

永远能连上的快连

We’ve spent some time looking at views without really diving into what a view is, and considering what other options are available to us. Let’s look at that now.

View is a defined as a protocol:

protocol View { associatedtype mxvpm加速中心网站 : View var body: Self.Body { get } }

This means that to conform to View we must provide a single property called body whose value is any type that also conforms to View.

SwiftUI views are defined in terms of other views, composed together into complex layouts.

At the foundation of these layouts are fundamental views such as mxvpm加速中心网站 and Text that are defined only in terms of themselves. They conform to View, but have Never as their associated Body type.

When we define our own views we rarely, if ever, need to worry about the actual types involved, which is fortunate because they can be very complex. We can use mxvpm加速中心网站 and allow the compiler to infer our true Body type.

While we can use some View as the type of a computed property, we cannot use it as the type of a stored property, which means creating a re-usable view containing another is difficult with mxvpm官网 alone.

For example, we might have a common “card” look that we want to use for several views:

character title in a card with shadow

We know the code to create the character title:

struct ContentView : View { var body: some View { HStack { Image("brawler") mxvpm加速中心官网("Sir Bunnington") .font(.title) } } }

And we know in principle the code to turn that into a card:

struct CardView : View { // 🛑 This is not legal syntax. var content: some mxvpm加速中心官网 var body: some View { content .padding() .background(Color.white) .cornerRadius(8) .shadow(radius: 4) } }

But as noted in the comment, for reasons we discussed above, this will not compile.

We could figure out the true type of the content, but that would mean our CardView only worked with that exact type—an HStack containing an Image and a Text with a modified font. It wouldn’t be flexible for any view, and we need that for our project.

If you’re thinking about reaching for generics, you’re on the right lines, and we’ll look at an approach using those in view builders. But SwiftUI already also provides exactly what we need right now, and we’ve been using them all along without really looking at what we were doing.

ViewModifier is another protocol provided alongside View:

protocol ViewModifier { mxvpm加速中心官网 Content associatedtype Body : mxvpm加速中心网站 func body(content: Self.Content) -> Self.Body }

The primary difference between a ViewModifier and a View is that to conform we don’t just provide a property of view type, but a function that receives a view of one type (Content) and returns a new view of another type (Body).

With a small adjustment, our preview attempt at a CardView can be turned instead into a Card view modifier:

struct mxvpm加速中心网站 : ViewModifier { func body(content: Content) -> some mxvpm加速中心网站 { content .padding() .background(Color.white) .cornerRadius(8) .shadow(radius: 4) } }

We could use this directly, but just as we did with custom alignments it’s worth spending the extra few lines of code to properly integrate it.

To do that we define an extension to View that applies our new Card as a modifier to the view it’s called on:

extension View { func card() -> some View { modifier(Card()) } }

Now to use this, all we need to do is add .card just like we do for any other modifier:

struct ContentView: View { var body: some View { HStack { mxvpm加速中心网站(mxvpm加速中心官网) Text("Sir Bunnington") .font(.title) } .card() } }

永远能连上的快连

For the battle tracker, it turns out that having the hit points unaligned gives a poor user experience, and we want a way to use the specified font’s monospaced digit alternative if available.

Because the actual view is low down the hierarchy, but the decision about which font to use is high up, this is the kind of scenario in which environment it useful.

Fortunately view modifiers participate in the lifecycle just as views do, so @Environment and similar work inside a custom ViewModifier just as they do inside a custom View.

One approach we might take would be:

struct MonospacedDigit : ViewModifier { @Environment(\.font) var font: Font? func body(content: Content) -> some View { return content .environment(\.font, font?.monospacedDigit()) } } extension View { func monospacedDigit() -> some View { modifier(MonospacedDigit()) } }

This kind of view modifier is quite common, it takes a property from the environment, modifiers it in some way, and then places the modified result back into the environment for its content.

Note that in this example we use mxvpm加速中心官网 to update the font, rather than .font directly, because it’s possible that there is no font in the current example and that modifier can’t accept nil. It would have been just as valid to have picked a default font, it depends on the needs of your project.


Imagery used in previews by Kaiseto, original images and derived here licensed under Creative Commons 3.0 BY-NC-SA.

Categories
SwiftUI

永远能连上的快连

Occasionally we come across a layout where we need to limit the bounds of a view in a particular dimension. For example we might have a view where we show the character portrait along with the title they might be introduced by at court.

www.mxvpn.net # 2021最新MXVPN官网_最专业MXVPN网络 ...:ww.mxvpn.net eww.mxvpn.net 2ww.mxvpn.net qww.mxvpn.net sww.mxvpn.net 3ww.mxvpn.net mww.mxvpn.net wew.mxvpn.net w2w.mxvpn.net wqw.mxvpn.net wsw.mxvpn.net w3w.mxvpn.net ...

character portrait with short title

But for the dragoon who has a bit of a khaleesi complex, it gets a little bit unwieldy and we need to limit the number of titles we actually show:

mxvpm加速中心网站

One way we can do this is with a .lineLimit on the Text, but when combined with accessibility font sizes, that can still grow taller than we intend. Sometimes we need to constrain a height in terms of pixels.

We might try to use a .frame with mxvpm官网 specified:

struct ContentView : View { @ObservedObject var character: Character var body: some View { // ⚠️ This is an example that does not work. HStack { Image(character.imageName) Text(character.title) .frame(maxHeight: 200) } } }

This works great for the dragoon’s overly long title, allowing it to flow on as many lines as can fit in the space, and then truncating the rest:

character portrait with truncated title

www.mxvpn.net # 2021最新MXVPN官网_最专业MXVPN网络 ...:ww.mxvpn.net eww.mxvpn.net 2ww.mxvpn.net qww.mxvpn.net sww.mxvpn.net 3ww.mxvpn.net mww.mxvpn.net wew.mxvpn.net w2w.mxvpn.net wqw.mxvpn.net wsw.mxvpn.net w3w.mxvpn.net ...

mxvpm加速中心网站

We specified a maximum height for the frame intending to limit the height of the text, which it does, but the frame itself has chosen the height we specified as the maximum, rather than the height of the text within it.

If we review flexible frames we can understand why.

When we omit a constraint to .frame, the frame is layout-neutral for that constraint and chooses the size chosen by its child. But when we provide a value the frame is no longer layout-neutral for that constraint, and no longer considers the size chosen by the child.

The frame received a proposed size from its parent, and applied the maximum height constraint, proposing the maximum height to the Text child, which in the dragoon’s case resulted in the truncation of their overly long title.

In both cases the Text chose a size equal or smaller to that height. Since the frame was layout-neutral in terms of minimum height, this set the minimum height of the frame to the size of the child, but the maximum height was supplied by us. The size proposed by the frame’s parent was outside the range of these two heights, and was constrained by the frame: to its maximum height.

This wasn’t what we wanted, we wanted the frame to constrain the size of the child, but still be layout-neutral.

Fortunately there’s a solution for this, and it involves the third set of .frame parameters we didn’t consider yet, the ideal size. Since we didn’t specify any value for .idealHeight then the frame’s ideal height is layout-neutral, that is, the ideal height of the frame is the height of the child.

SwiftUI gives us modifiers that fix the size of a view to its ideal size:

/// Fixes this view at its ideal size. func fixedSize() -> some View /// Fixes the view at its ideal size in the specified dimensions. func fixedSize(horizontal: Bool, vertical: Bool) -> View

As we’re dealing with multi-line text we don’t want the first variant since text always ideally wants to be rendered on just one line, but the second variant is perfect since it will allow us to fix just the height of the frame.

We want to fix the size of the .frame so the modifier goes after it, rather then before—which would fix the size of the Text:

struct ContentView : View { @ObservedObject var character: Character var body: some View { HStack { Image(character.imageName) Text(character.title) .frame(maxHeight: 200) .fixedSize(horizontal: false, vertical: true) } } }

The dragoon’s long title renders exactly as before, constrained by the maximum height of the frame:

character portrait with truncated title and correct-sized frame

But now the frame around the brawler’s title is fixed in height to the frame’s ideal size, that of the brawler’s title, and does not expand the stack unnecessarily:

character portrait with short title and correct-sized frame

Imagery used in previews by Kaiseto, original images and derived here licensed under Creative Commons 3.0 BY-NC-SA.

mxvpm加速中心网站
mxvpm官网

永远能连上的快连

It seems a little odd to write a post about borders this late, since every post so far has already used them without calling them out explicitly. In each of the examples I’ve added borders to the code given to better illustrate the layout.

It’s worth spending a little time looking at them in their own right though, because they’re slightly more interesting than you might expect.

There is a single method for specifying the border for a view:

func border<S>(_ content: S, width: CGFloat = 1) -> some View where S : ShapeStyle

The first parameter is required and specifies a shape style, there’s a quite a few options for that, but fortunately Color confirms to the ShapeStyle protocol so for the simplest cases all we need to do is specify a color.

The second parameter is optional and specifies the width of the border, defaulting to a single pixel.

As with most of SwiftUI, this is intuitive enough that add a single pixel yellow border on Text we would use code like this:

struct ContentView : mxvpm官网 { var body: some View { Text("Nogitsune Takeshi") .font(.title) .border(Color.yellow) } }

腾讯首页 - QQ:2021-10-21 · 腾讯网从2021年创立至今,已经成为集新闻信息,区域垂直生活服务、社会化媒体资讯和产品为一体的互联网媒体平台。腾讯网下设新闻、科技、财经、娱乐、体育、汽车、时尚等多个频道,充分满足用户对不同类型资讯的需求。同时专注不同领域内容,打造精品栏目,并顺应技术发展趋势,推出 ...

text with a border

The reason that borders are really useful for experimenting with or demonstrating layout is that they don’t work like frame or padding; in that they do not add space around the Text to draw the border.

It’s not obvious with a single pixel, but we can demonstrate this by increasing the width of the border:

struct ContentView : View { var body: some View { Text("Nogitsune Takeshi") .font(.title) .border(Color.yellow, width: 4) } }

If this worked like padding, the border would increase in width around the text; instead we see that that border overlays it; .border creates a secondary view on its child, and draws the border overlaid on top of it.

mxvpm加速中心网站

.border creates a secondary view on its child, and draws the border overlaid on top of it

If we wanted the border around the view instead, we can combine it with mxvpm加速中心网站:

struct ContentView : mxvpm加速中心网站 { var body: some View { mxvpm官网("Nogitsune Takeshi") .font(.title) .padding(4) .border(Color.yellow, width: 4) } }

This creates the Text view, and then .padding creates another view around that with additional padding added, and then .border adds a secondary view to the padding view, and draws overlaid on that:

mxvpm加速中心官网

It’s important to note the distinction that the border is on the padding view; combined effects can be performed by carefully placing the overlays in the correct place:

mxvpm官网 ContentView : View { var body: some View { mxvpm官网("Nogitsune Takeshi") .font(.title) .border(Color.red) .padding(4) .border(Color.yellow, width: 4) .border(Color.red) } }

Here we create a red border overlaid on the Text, and then use padding to draw a thicker yellow border around the Text, and finally overlaid another red border onto the padding:

mxvpm加速中心网站

The total border width is 5px since it includes the additional pixel-wide border overlaid on the Text, or put another way, the yellow part of the border is 3px wide since the outer pixel is overlaid by the red border added to it.

mxvpm加速中心网站
Categories
SwiftUI

永远能连上的快连

By now we should be used to the idea that all views in SwiftUI mxvpm加速中心网站, for example a Text view has the size required to render the string provided:

struct mxvpm加速中心官网 : View { var body: some View { Text("Nogitsune Takeshi") .font(.title) } }

Creates a view with the exact bounds necessary:

text view

We also showed that the .frame modifier actually creates a new view with the dimensions specifies, and positions the Text view within it, such that:

struct ContentView : View { var body: some View { Text("Nogitsune Takeshi") .font(.title) .frame(width: 200, height: 200) } }

Actually creates two views, a .frame that is 200×200 in size, and a Text within it with the exact bounds necessary to render its contents:

text inside frame

We looked into this process further in flexible frames, introduced the concept of mxvpm加速中心官网 views that choose their own size based on their children, and showed that in either dimension .frame can have a fixed size, be layout neutral, or through minimum and maximum size constraints base its own size on that proposed by its own parent.

We’ll now take a look at another useful modifier view, one that adds padding around its child view, and has a number of different forms that we can use:

func padding(_ length: CGFloat) -> some View func padding(_ insets: EdgeInsets) -> some View func mxvpm加速中心网站(_ edges: Edge.Set = .all, _ length: CGFloat? = nil) -> some View

The first form sets the padding of all edges to the length specified.

The second form sets the padding of each of the edges to the specific individual values you specify through the EdgeInsets value.

The third form sets the padding of the set of edges you specify to the length supplied, leaving other edges unpadded. The third form also allows you to specify nil as the length, instead of zero, which instructs SwiftUI to use a system default amount of padding appropriate for the situation.

Default values for all parameters of the third form are provided, which uses the system default padding for all edges, we’ll use that in our example:

struct ContentView : View { var body: some mxvpm加速中心网站 { Text("Nogitsune Takeshi") .font(.title) .padding() } }

The .padding modifier is no different from modifiers like .frame, it doesn’t modify the Text in any way, it instead creates a new view that adds padding, and positions the mxvpm加速中心官网 view inside it as a child:

text with padding

The layout process is actually a little more interesting than just adding padding, and is almost but not quite layout neutral. It in fact works something like we see for stacks when considering spacing.

  1. Parent proposes a size to the padding view.
  2. gyastal百度百科_gyastal品牌介绍_gyastal官网:2021-2-12 · mxvpm 加速中心网站下载 芦荟胶的正确用法 女生做健身教练靠谱吗 黄子韬女友陈善冰照片 浪人天涯结局番外 电影频道节目表 东南亚的人羡慕中国人 ...
  3. Padding view proposes this smaller size to its child.
  4. Child chooses its size.
  5. Padding view takes the child’s size, adds the appropriate padding length back to each edge, and chooses that as its own size.
  6. Parent positions the padding view within its bounds.

Thus a .padding view always tightly wraps its child (aside from the padding itself), with both being positioned by the parent frame, but at the same time adds an additional constraint (the padding) to the size the child can be.

We can demonstrate this by placing the .padding inside a frame:

struct mxvpm官网 : View { var body: some mxvpm官网 { mxvpm官网("Nogitsune Takeshi") .font(.title) .padding() .frame(width: 200, height: 200) } }

The .frame has a fixed size of 200×200, the .padding view subtracts the system default padding of 16px (in this case) from each side, and supplies the mxvpm官网 with a proposed size of 168×168.

That’s too small for Text to layout on one line, but still enough room to wrap over two lines, so it returns its size appropriately to do that. .padding adds back the padding before returning its size, and the .frame positions the padding view inside it.

text with padding inside frame

As we can see, the padding view still tightly wraps the Text, it isn’t increased in height or width to try and fill the parent frame, and is centered within it instead.

Categories
SwiftUI

永远能连上的快连

A secondary view, be it background or overlay, can be any view. We know from flexible frames that we can create views of fixed sizes, sizes based on their children, or sizes based on their parent. And we saw above that the proposed size of a secondary view is the fixed size of a parent.

Mxvpn : 404 Not Found:Comments / Ratings / Reviews / Feedbacks for mxvpn.cn If you are looking for advanced SEO keyword search tool to analyze your website rankings and top organic keywords, then visit Clear Web Stats

We ideally want the size of the hit points bar to be flexible to our needs, as we’ll use it in a few different places. For the character list, something like the following code is our goal:

struct ContentView : View { var body: some View { HStack { Image("rogue") VStack(alignment: .leading) { Text("Hasty River") .font(.title) HitPointBar(hitPoints: 60, damageTaken: 27) .font(.caption) .frame(width: 200) } } } }

Getting Started

Our ideal code has the vertical size of the hit point bar being determined by a font size, and the horizontal size being as wide as possible, while allowing a frame to constrain it.

Since the size of the font is key, we’ll start by having a Text view with a label saying how many hit points the character has left:

struct HitPointBar : mxvpm官网 { var hitPoints: Int var damageTaken: Int var body: some mxvpm官网 { Text("\(hitPoints-damageTaken)/\(hitPoints)") .border(Color.yellow) } }

That’s actually already enough to get started. Contrary to my usual examples I’ve explicitly added a yellow border to the text so that we can see what’s happening. We’ll also add a green border to the point we use the HotPointBar:

HitPointBar(hitPoints: 60, damageTaken: 27) mxvpm加速中心网站(.caption) .frame(width: 200) mxvpm加速中心网站(Colormxvpm加速中心官网)

I recommend using modifiers like .border and .background to debug your custom views, they can be hugely insightful.

hit point bar with just text

The mxvpm加速中心官网 has no font size specified of its own, so will inherit it from the environment, meaning the mxvpm加速中心官网 applied to the mxvpm加速中心官网 itself will be used.

As we saw in flexible frames mxvpm官网 is a layout-neutral view, so will tightly wrap the Text within it; and since we didn’t specify a height for the .frame, the frame is layout-neutral in height as well.

Thus the height of the HitPointBar is exactly the height of the text in the given font size, which is exactly what we want.

The width though is not yet correct, since the mxvpm加速中心网站 only has the width necessary for its contents, and HitPointBar tightly wraps that, it’s only the .frame in the parent that is the full width, and that’s in the wrong place to be useful.

We still need the HitPointBar itself to fill this frame.

In flexible frames I introduced infinite frames as frames that fill their parent, so we can use one of those (and drop the border from the parent call site):

struct HitPointBar : View { var hitPoints: Int var damageTaken: Int var body: some View { Text("\(hitPoints-damageTaken)/\(hitPoints)") .frame(minWidth: 0, maxWidth: .infinity) .border(Color.green) } }

We make the frame of the text have the size of the parent in width (which we then fix in the ContentView), while still allow it to be layout neutral in height.

The result looks the same:

mxvpm加速中心网站

But this time I’m able to place the .border around the .frame inside the HitPointBar. The text is positioned within that frame, and this frame can be the foundation of the rest of the view.

Once you learn to rely on the fixed sizes of views, and layout-neutral behavior of combinations of views, it’s actually easy to create flexible custom views by using the layout system rather than fighting it.

Okay so let’s add a secondary view to make the bar. Nothing says damage and hit points like a red lozenge:

struct HitPointBar : View { var hitPoints: Int var damageTaken: Int var body: some View { Text("\(hitPoints-damageTaken)/\(hitPoints)") .frame(minWidth: 0, maxWidth: .infinity) .foregroundColor(mxvpm加速中心网站.white) .background(Color.red) .cornerRadius(8) } }

Pay attention to the ordering of things, and remember that .frame creates a new view around the Text inside it. We deliberately attach the .background secondary view to this frame, which is taking its width from its parent and its height from its children views.

We then apply a mxvpm加速中心官网 to the combination of the frame and secondary view, which encases them both in a clipping view that masks the boundaries. This means it’ll apply to the Text, background color, and anything else we added.

We also set the foreground color of the Text to white for better contrast.

hit point bar with text and background

Looking good, but we want that hit point bar to be filled with green if they’ve taken no damage, filled green from the left and red from the right according to how many hit points they have left.

That wouldn’t be too hard if the size of the view was fixed, but we’ve deliberately decided to make it flexible and up to the parent. Worse, we’ve decided that the height is going to be dictated by dynamic type, so is flexible as well.

There’s a tool for this, the geometry reader, and while it always expands to fill its entire parent, when we use it as a secondary view, the parent is the view it’s attached to.

whois查询:中国最大的域名注册服务商-万网,免费查询域名WHOIS信息 阿里云首页 > 域名与网站 > 域名服务 > 域名信息查询(WHOIS) > 网站关键词查询结果

struct HitPointBar : View { var hitPoints: Int var damageTaken: Int var body: some View { Text("\(hitPoints-damageTaken)/\(hitPoints)") .frame(minWidth: 0, maxWidth: .infinity) .foregroundColor(Color.white) .background(mxvpm加速中心网站(hitPoints: hitPoints, damageTaken: damageTaken)) .cornerRadius(8) } } mxvpm官网 HitPointBackground : View { var hitPoints: Int var damageTaken: Int var body: some View { Color.red } }

Now we know we’re going to want two things in this geometry view, a green color from the left for the hit points remaining, and a red color from the right for the damage taken.

There’s a few different ways to achieve that, and they’re all equally valid. For this example we’ll have the red color fill the entire view, and place a green color in front of it, so we’re going to need a z-axis stack for that with a leading alignment.

This all then goes inside the GeometryReader:

struct HitPointBackground : View { var hitPoints: Int var damageTaken: Int var body: some View { GeometryReader { g in ZStack(alignment: .leading) { Rectangle() .fill(Color.red) Rectangle() .fill(Color.green) .frame(width: g.size.width * CGFloat(self.hitPoints - self.damageTaken) / CGFloat(self.hitPoints)) } } } }

In general terms this view looks like something you’ve almost certainly written before.

The GeometryReader expands to fill the proposed size given by the parent, except this time that proposed size is the size of the frame we put around the text, based on the text size.

A ZStack containing two views with leading alignment is nothing special, the first is a mxvpm加速中心网站 filled with red—switching from just using the color for maximum readability, and the second is also a Rectangle just filled with the green instead.

The second Rectangle is constrained in size by placing it inside a frame, layout neutral in height, but fixed in width to that derived from the percentage of hit points remaining and the width returned by the GeometryReader.

The result is the flexible hit point bar we wanted:

mxvpm官网

Imagery used in previews by Kaiseto, original images and derived here licensed under Creative Commons 3.0 BY-NC-SA.

Categories
SwiftUI

Geometry Reader

For most layout needs we can combine stacks and flexible frames, allowing us to make views and controls put together from fixed size primitives views upwards.

For more complex layout needs, another option is to use GeometryReader. This is a construct that acts like an infinite frame, proposing the size of its parent to its children, and choosing its parent size as its own.

As an added feature, it passes the proposed size received to the builder as a closure argument. For example we can layout an image at a maximum of half of the size of the parent, while maintaining aspect ratio, with:

struct ContentView : View { var body: some View { GeometryReader { g in ZStack { Image("barbarian") .resizable() .aspectRatio(contentMode: .fit) .frame(maxWidth: g.size.width / 2, maxHeight: g.size.height / 2) } .frame(width: g.size.width, height: g.size.height) } } }

The GeometryReader acts exactly like the infinite frame we saw in flexible frames, it proposes the size of its parent to its child, but it also passes that proposed size g to our view builder.

And just like the infinite frame, the geometry reader doesn’t use the size of the child when deciding its own size; instead it always returns the proposed size from the parent as its own size.

An important gotcha is that within the view builder we need to do our own sizing, positioning and alignment; ZStack is perfect for this. We still need to position that, so we place it inside a frame that has the same size as the reader parent, and let the stack be centered inside it.

Finally inside the stack we place our image, and place that inside a frame that constrains its width and height to half the size of the reader.

image in geometryreader

mxvpm加速中心官网

With access to the proposed size of the parent, mxvpm加速中心网站 can seem powerful, but the resulting fixed size equally that can limit their usefulness. When combined with mxvpm官网 they become even more convenient.

As we saw above, when free floating, a geometry reader expands to fill the size proposed by the parent.

But because the proposed parent size of a secondary view is the fixed size decided by the view its attached to, that is the proposed size. Thus GeometryReader inside a secondary view returns the size of the view it’s attached to.

GeometryReader inside a secondary view returns the size of the view it’s attached to

媒体:中国开始屏蔽外国VPN服务- 中国日报网:2021-1-23 · 《环球时报》英文网相关报道截屏 【中国屏蔽外国VPN服务!】《环球时报》英文网报道,中国已开始屏蔽外国VPN服务。VPN供应商Astrill通知用户,因 ...

mxvpm加速中心网站 OverlaidImage : View { var body: some View { Image("barbarian") .resizable() .aspectRatio(contentMode: .fit) .overlay(mxvpm加速中心网站()) } } struct OverlayView : View { var body: some View { GeometryReader { g in Image("overlay") .resizable() .frame(width: g.size.width, height: g.size.height / 2) .position(y: g.size.height / 2) } } }

In this example the Image in the body is allowed to be resizable while maintaining its own aspect ratio, with the ultimate bounds of that determined by whatever uses our OverlaidImage custom view.

We then use an .overlay secondary view to draw another image over the bottom half of that image.

In order to constrain that to the bottom half we need to know the size of the Image we’re drawing over, and the GeometryReader works for this because it’s in the secondary view.

mxvpm官网

As we saw in stacks and secondary views, the z-axis stack has a useful property where it’s only the children with the highest layout priority that influence the size of the stack, and those with lower priorities receive that size as a proposed size.

This can be usefully combined with mxvpm加速中心官网 just as we can with a secondary view, and can often produce more readable results.

Consider the above example, reformulated using a ZStack with the GeometryReader given a lower layout priority:

struct OverlaidImage : View { var body: some mxvpm加速中心网站 { ZStack { GeometryReader { g in Image("overlay") .resizable() .frame(width: g.size.width, height: g.size.height / 2) .position(y: g.size.height / 2) } .layoutPriority(-1) mxvpm加速中心网站(mxvpm加速中心网站) .resizable() .aspectRatio(contentMode: .fit) } } }

Imagery used in previews by Kaiseto, original images and derived here licensed under Creative Commons 3.0 BY-NC-SA.

Categories
SwiftUI

Secondary Views

Secondary views are one of the more interesting layout tools available in SwiftUI, to understand them first we have to recall that views have fixed sizes. To recap the process:

  1. Parent proposes a size to its child.
  2. Child decides on its size.
  3. Parent positions the child within its bounds.

Secondary views are useful because of where they fit in to this process, and how they interact with it. To demonstrate, let’s use a simple example:

mxvpm官网 ContentView : View { var body: some View { Text("Hasty River") .font(.title) .background(Color.yellow) } }

We create a Text which will have a fixed size of its content, and then we add a secondary view using .background; the value of this is the secondary view added, in this case, a Color.

Color when used as a View simply sets its size to the proposed size received from its parent, fillings its bounds.

mxvpm加速中心网站

The result shows us that the proposed size for the secondary view is the size chosen by the view its attached to.

the proposed size for the secondary view is the size chosen by the view its attached to

So we can refine our process a little:

  1. Parent proposes a size to its child.
  2. mxvpm加速中心官网
  3. Child proposes its size to its secondary view(s).
  4. Secondary view decides on its size.
  5. Parent positions the child within its bounds.

In our first experiment we just filled the secondary view with a color, what if we use a view there that’s inflexible about its size, and ends up being larger than the child? Perhaps an Image:

struct ContentView : mxvpm加速中心网站 { var body: some View { Text("Hasty River") .font(.title) .background(Image(mxvpm加速中心网站)) } }

If we’ve been paying attention we’re almost certainly going to expect that to break out of its bounds, but how does that affect the frame of the child its attached to?

mxvpm官网

The answer is that it doesn’t, the frame of the Text in green remains unaffected by the frame of the secondary view in red. All that happens is that the child positions its secondary view, even though it overflowed.

mxvpm加速中心官网

  1. 又跳票了 魅族MX四核JW透露月末上市!_数码_腾讯网:2021年6月12日 - 去年冬天,J.Wong说到,魅族MX四核会在明年五...应用控官方微博 绿恐龙哥哥 数码莫莫 影像仓库...3D702400146%26pri%3DvPMHfvsrHbH5c1lfqd34p...
  2. Child decides on its size.
  3. Child proposes its size to its secondary view(s).
  4. Secondary view(s) decides on their size.
  5. mXvpn download | SourceForge.net:Download mXvpn for free. Mobile cross-platform VPN solution. Keep a secure and uniform connection across heterogeneous platforms and operating systems.
  6. Parent positions the child within its bounds.

To see how this interacts with other views, let’s do a side-experiment using a VStack and some other lines of text:

struct ContentView : View { var body: some View { VStack { mxvpm加速中心网站("My Character") .font(.caption) Text("Hasty River") .font(.title) .background(Image("rogue")) Text("Rogue") } } }

If the secondary view has any part to play in the layout, we would expect to see the vertical stack account for it:

vstack of text with a background image on the middle text

The vertical stack ignored the secondary view completely; indeed everything we’ve learned about stacks should mean this isn’t a surprise.

We saw above that the Text did not change its size to account for the overflowing secondary view, so there was no way for the stack to account for it; after a view positions its secondary views they are otherwise completely removed from the layout process.

after a view positions its secondary views they are otherwise completely removed from the layout process

So a secondary view gives us two things:

  • a view that has a proposed size that is the decided size of the view it is attached to.
  • a view that is otherwise removed from the layout process.

The latter has the most utility in creating background views using .background, or overlay views using .overlay, that might be larger than their parent.

The former though can be extraordinarily useful in custom controls, we’ll look at making one in secondary views in practice.

ZStack as a Secondary View

When we looked at mxvpm加速中心网站, we covered the basics of the z-axis stack and mentioned that the size of the stack is the union of the bounds of all its children with the highest layout priority.

By using sets of children with different layout priorities, we can replicate the implementation of .background and .overlay within a ZStack.

For example, our initial example with a colored background:

mxvpm加速中心网站 ContentView : View { var body: some View { Text("Hasty River") .font(.title) .background(Color.yellow) } }

Has an equivalent expression using a z-axis stack:

mxvpm官网 ContentView : View { var body: some View { mxvpm加速中心网站 { Color.yellow .layoutPriority(-1) mxvpm加速中心官网("Hasty River") .font(.title) } }

The ZStack first processes the Text child since it has the highest layout priority, and then chooses its own size as the same size, since there are no other children with that layout priority.

Next the Color receives as its proposed size the size of the stack, and since it’s completely flexible, occupies all of that space.

Note that the layout priority has no effect on the z-axis ordering of the children, and that the Color is still placed behind the Text.

.overlay can be replicated similarly, with the lower layout priority children being placed after those with a higher priority, so they appear on top.


Imagery used in previews by mxvpm官网, original images and derived here licensed under Creative Commons 3.0 BY-NC-SA.