You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Layout协议重写SwiftUI的frame(width:height:alignment:)修饰符?

重写SwiftUI的frame(width:height:alignment:)修饰符问题

我正尝试重写SwiftUI的frame(width:height:alignment:)修饰符,准确说是一个接收非可选值的简化版本。目标是让以下代码在传入任意对齐方式(包括自定义对齐)时,用myFrame替代frame能产生完全相同的效果:

Rectangle()
    .fill(.red)
    .frame(width: 100, height: 100)
    .myFrame(width: 250, height: 250, alignment: .bottomTrailing)
    .border(.blue)

以下是我目前的尝试:

extension View {
    func myFrame(width: CGFloat, height: CGFloat, alignment: Alignment) -> some View {
        MyFrame(size: CGSize(width: width, height: height), alignment: alignment) {
            // make sure we only pass in a single view
            VStack { self }
        }
    }
}

private struct MyFrame: Layout {
    let size: CGSize
    let alignment: Alignment
    
    func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
        size
    }
    
    func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
        let dimensions = subviews[0].dimensions(in: ProposedViewSize(size))
        let pointInSubview = CGPoint(
            x: dimensions[alignment.horizontal],
            y: dimensions[alignment.vertical]
        )
        let position = /* somehow use `pointInSubview` to figure out where to place the subview */
        subviews[0].place(at: position, proposal: ProposedViewSize(size))
    }
}

确定子视图在自身局部坐标系中的对齐位置很容易,但问题是如何确定在给定CGRect bounds中的对应位置。这能否实现,或是Layout协议无法完成此操作?


解决方案

完全可以用Layout协议实现这个需求,核心是要计算出父容器bounds中对齐点的坐标,再结合子视图自身的对齐点,算出子视图的放置位置。

修正后的实现代码

extension View {
    func myFrame(width: CGFloat, height: CGFloat, alignment: Alignment) -> some View {
        MyFrame(size: CGSize(width: width, height: height), alignment: alignment) {
            self
        }
    }
}

private struct MyFrame: Layout {
    let size: CGSize
    let alignment: Alignment
    
    func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
        size
    }
    
    func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
        guard !subviews.isEmpty else { return }
        
        // 获取子视图的理想尺寸(基于父容器的size提议)
        let subviewSize = subviews[0].sizeThatFits(ProposedViewSize(size))
        // 计算子视图自身的对齐锚点坐标
        let subviewAnchor = CGPoint(
            x: subviewSize.width * alignment.horizontal.fraction,
            y: subviewSize.height * alignment.vertical.fraction
        )
        // 计算父容器bounds中的对齐锚点坐标
        let containerAnchor = CGPoint(
            x: bounds.minX + size.width * alignment.horizontal.fraction,
            y: bounds.minY + size.height * alignment.vertical.fraction
        )
        // 子视图的放置位置 = 容器锚点 - 子视图锚点(让两个锚点重合)
        let placementPosition = CGPoint(
            x: containerAnchor.x - subviewAnchor.x,
            y: containerAnchor.y - subviewAnchor.y
        )
        
        subviews[0].place(at: placementPosition, proposal: ProposedViewSize(size))
    }
}

关键逻辑说明

  • 子视图锚点计算:利用Alignment的horizontal和vertical分量的fraction属性(0到1之间的值),乘以子视图的宽高,得到子视图自身坐标系中对齐点的位置。
  • 容器锚点计算:用同样的fraction值,结合父容器bounds的起始坐标和尺寸,算出容器坐标系中对应对齐点的位置。
  • 放置位置计算:让子视图的对齐点和容器的对齐点重合,所以放置位置等于容器锚点减去子视图锚点。

额外优化点

  • 移除了原代码中多余的VStack,因为Layout的闭包本身可以直接传入单个视图,不需要额外包装。
  • 增加了空subviews的安全判断,避免数组越界。

这个实现完全匹配原生frame(width:height:alignment:)的行为,包括自定义对齐方式的支持。

内容的提问来源于stack exchange,提问作者Tim Vermeulen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 13:10:38