release(ios): 0.2.2 构建 10 修消息卡空洞、标签胶囊假宽与表单标题层级

- 消息卡片删除按钮移出正文行,改用 overlay 悬在右侧,44pt 点击区不再把行撑高
- 批量标签胶囊由等宽栅格改为流式布局,新增 AIFlowLayout 并让节点页复用同一实现
- 设置页应用信息补「构建」一行,版本号全程不带括号
- 添加实例与发送消息两张表单表统一行内标题,去掉大标题留下的空白带
- 节点空态文案改为「节点未返回设备信息」,与量表条实际返回的数据不矛盾
- 图标石墨底、面板阴影与插槽统一压暖 3~4 级,全图 0 蓝色像素
This commit is contained in:
chick
2026-09-15 13:03:23 +08:00
parent c3d17b3daf
commit 802ddd1102
16 changed files with 138 additions and 82 deletions
@@ -862,3 +862,63 @@ public struct AISymbolAction: View {
.accessibilityLabel(title)
}
}
/// 按固有尺寸换行排布的流式布局:标签胶囊这类「宽度取决于文字」的控件用它。
/// 栅格会把每枚胶囊拉到同一列宽,短标签右侧就空出一片,读起来像没加载完。
public struct AIFlowLayout: Layout {
public let spacing: CGFloat
public init(spacing: CGFloat = 8) {
self.spacing = spacing
}
public func sizeThatFits(
proposal: ProposedViewSize,
subviews: Subviews,
cache: inout ()
) -> CGSize {
let maxWidth = proposal.width ?? .infinity
var x: CGFloat = 0
var y: CGFloat = 0
var rowHeight: CGFloat = 0
var widestRow: CGFloat = 0
for subview in subviews {
let size = subview.sizeThatFits(.unspecified)
if x > 0, x + size.width > maxWidth {
widestRow = max(widestRow, x - spacing)
y += rowHeight + spacing
x = 0
rowHeight = 0
}
x += size.width + spacing
rowHeight = max(rowHeight, size.height)
}
widestRow = max(widestRow, x - spacing)
return CGSize(width: max(0, widestRow), height: max(0, y + rowHeight))
}
public func placeSubviews(
in bounds: CGRect,
proposal: ProposedViewSize,
subviews: Subviews,
cache: inout ()
) {
var x = bounds.minX
var y = bounds.minY
var rowHeight: CGFloat = 0
for subview in subviews {
let size = subview.sizeThatFits(.unspecified)
if x > bounds.minX, x + size.width > bounds.maxX {
x = bounds.minX
y += rowHeight + spacing
rowHeight = 0
}
subview.place(at: CGPoint(x: x, y: y), proposal: .unspecified)
x += size.width + spacing
rowHeight = max(rowHeight, size.height)
}
}
}