London Escorts sunderland escorts 1v1.lol unblocked yohoho 76 https://www.symbaloo.com/mix/yohoho?lang=EN yohoho https://www.symbaloo.com/mix/agariounblockedpvp https://yohoho-io.app/ https://www.symbaloo.com/mix/agariounblockedschool1?lang=EN
0 C
New York
Thursday, January 30, 2025

Working with percentages in SwiftUI format – Ole Begemann


SwiftUI’s format primitives typically don’t present relative sizing choices, e.g. “make this view 50?% of the width of its container”. Let’s construct our personal!

Use case: chat bubbles

Think about this chat dialog view for instance of what I need to construct. The chat bubbles at all times stay 80?% as broad as their container because the view is resized:

The chat bubbles ought to turn into 80?% as broad as their container. Obtain video

Constructing a proportional sizing modifier

1. The Format

We will construct our personal relative sizing modifier on prime of the Format protocol. The format multiplies its personal proposed dimension (which it receives from its guardian view) with the given elements for width and top. It then proposes this modified dimension to its solely subview. Right here’s the implementation (the complete code, together with the demo app, is on GitHub):

/// A customized format that proposes a share of its
/// acquired proposed dimension to its subview.
///
/// - Precondition: should include precisely one subview.
fileprivate struct RelativeSizeLayout: Format {
    var relativeWidth: Double
    var relativeHeight: Double

    func sizeThatFits(
        proposal: ProposedViewSize, 
        subviews: Subviews, 
        cache: inout ()
    ) -> CGSize {
        assert(subviews.depend == 1, "expects a single subview")
        let resizedProposal = ProposedViewSize(
            width: proposal.width.map { $0 * relativeWidth },
            top: proposal.top.map { $0 * relativeHeight }
        )
        return subviews[0].sizeThatFits(resizedProposal)
    }

    func placeSubviews(
        in bounds: CGRect, 
        proposal: ProposedViewSize, 
        subviews: Subviews, 
        cache: inout ()
    ) {
        assert(subviews.depend == 1, "expects a single subview")
        let resizedProposal = ProposedViewSize(
            width: proposal.width.map { $0 * relativeWidth },
            top: proposal.top.map { $0 * relativeHeight }
        )
        subviews[0].place(
            at: CGPoint(x: bounds.midX, y: bounds.midY), 
            anchor: .middle, 
            proposal: resizedProposal
        )
    }
}

Notes:

  • I made the kind personal as a result of I need to management how it may be used. That is essential for sustaining the idea that the format solely ever has a single subview (which makes the maths a lot less complicated).

  • Proposed sizes in SwiftUI will be nil or infinity in both dimension. Our format passes these particular values via unchanged (infinity occasions a share continues to be infinity). I’ll focus on beneath what implications this has for customers of the format.

2. The View extension

Subsequent, we’ll add an extension on View that makes use of the format we simply wrote. This turns into our public API:

extension View {
    /// Proposes a share of its acquired proposed dimension to `self`.
    public func relativeProposed(width: Double = 1, top: Double = 1) -> some View {
        RelativeSizeLayout(relativeWidth: width, relativeHeight: top) {
            // Wrap content material view in a container to verify the format solely
            // receives a single subview. As a result of views are lists!
            VStack { // alternatively: `_UnaryViewAdaptor(self)`
                self
            }
        }
    }
}

Notes:

  • I made a decision to go together with a verbose identify, relativeProposed(width:top:), to make the semantics clear: we’re altering the proposed dimension for the subview, which received’t at all times end in a special precise dimension. Extra on this beneath.

  • We’re wrapping the subview (self within the code above) in a VStack. This may appear redundant, but it surely’s crucial to verify the format solely receives a single factor in its subviews assortment. See Chris Eidhof’s SwiftUI Views are Lists for an evidence.

Utilization

The format code for a single chat bubble within the demo video above appears to be like like this:

let alignment: Alignment = message.sender == .me ? .trailing : .main
chatBubble
    .relativeProposed(width: 0.8)
    .body(maxWidth: .infinity, alignment: alignment)

The outermost versatile body with maxWidth: .infinity is chargeable for positioning the chat bubble with main or trailing alignment, relying on who’s talking.

You possibly can even add one other body that limits the width to a most, say 400 factors:

let alignment: Alignment = message.sender == .me ? .trailing : .main
chatBubble
    .body(maxWidth: 400)
    .relativeProposed(width: 0.8)
    .body(maxWidth: .infinity, alignment: alignment)

Right here, our relative sizing modifier solely has an impact because the bubbles turn into narrower than 400 factors. In a wider window the width-limiting body takes priority. I like how composable that is!

80?% received’t at all times end in 80?%

For those who watch the debugging guides I’m drawing within the video above, you’ll discover that the relative sizing modifier by no means studies a width better than 400, even when the window is broad sufficient:


A Mac window showing a mockup of a chat conversation with bubbles for the speakers. Overlaid on the chat bubbles are debugging views showing the widths of different components. The total container width is 753. The relW=80% debugging guide shows a width of 400.
The relative sizing modifier accepts the precise dimension of its subview as its personal dimension.

It’s because our format solely adjusts the proposed dimension for its subview however then accepts the subview’s precise dimension as its personal. Since SwiftUI views at all times select their very own dimension (which the guardian can’t override), the subview is free to disregard our proposal. On this instance, the format’s subview is the body(maxWidth: 400) view, which units its personal width to the proposed width or 400, whichever is smaller.

Understanding the modifier’s habits

Proposed dimension ? precise dimension

It’s essential to internalize that the modifier works on the idea of proposed sizes. This implies it relies on the cooperation of its subview to realize its aim: views that ignore their proposed dimension will likely be unaffected by our modifier. I don’t discover this significantly problematic as a result of SwiftUI’s complete format system works like this. In the end, SwiftUI views at all times decide their very own dimension, so you may’t write a modifier that “does the appropriate factor” (no matter that’s) for an arbitrary subview hierarchy.

nil and infinity

I already talked about one other factor to concentrate on: if the guardian of the relative sizing modifier proposes nil or .infinity, the modifier will move the proposal via unchanged. Once more, I don’t assume that is significantly dangerous, but it surely’s one thing to concentrate on.

Proposing nil is SwiftUI’s means of telling a view to turn into its ultimate dimension (fixedSize does this). Would you ever need to inform a view to turn into, say, 50?% of its ultimate width? I’m unsure. Possibly it’d make sense for resizable photos and comparable views.

By the way in which, you can modify the format to do one thing like this:

  1. If the proposal is nil or infinity, ahead it to the subview unchanged.
  2. Take the reported dimension of the subview as the brand new foundation and apply the scaling elements to that dimension (this nonetheless breaks down if the kid returns infinity).
  3. Now suggest the scaled dimension to the subview. The subview would possibly reply with a special precise dimension.
  4. Return this newest reported dimension as your individual dimension.

This strategy of sending a number of proposals to little one views is named probing. Plenty of built-in containers views do that too, e.g. VStack and HStack.

Nesting in different container views

The relative sizing modifier interacts in an fascinating means with stack views and different containers that distribute the out there area amongst their youngsters. I believed this was such an fascinating matter that I wrote a separate article about it: How the relative dimension modifier interacts with stack views.

The code

The entire code is obtainable in a Gist on GitHub.

Digression: Proportional sizing in early SwiftUI betas

The very first SwiftUI betas in 2019 did embrace proportional sizing modifiers, however they have been taken out earlier than the ultimate launch. Chris Eidhof preserved a duplicate of SwiftUI’s “header file” from that point that reveals their API, together with fairly prolonged documentation.

I don’t know why these modifiers didn’t survive the beta section. The discharge notes from 2019 don’t give a cause:

The relativeWidth(_:), relativeHeight(_:), and relativeSize(width:top:) modifiers are deprecated. Use different modifiers like body(minWidth:idealWidth:maxWidth:minHeight:idealHeight:maxHeight:alignment:) as a substitute. (51494692)

I additionally don’t keep in mind how these modifiers labored. They most likely had considerably comparable semantics to my resolution, however I can’t make certain. The doc feedback linked above sound simple (“Units the width of this view to the required proportion of its guardian’s width.”), however they don’t point out the intricacies of the format algorithm (proposals and responses) in any respect.

Related Articles

Social Media Auto Publish Powered By : XYZScripts.com