如何在SwiftUI的HStack中均匀排列视图并优化冗余代码?
问题:SwiftUI中实现元素均匀分布的HStack优化
我在SwiftUI视图中用HStack展示内容,需要让所有元素之间及两侧均匀分配空间,最初的实现是在n个元素前后添加n+1个Spacer(),代码如下:
import SwiftUI struct ContentView: View { var element_1: some View { Circle() .frame(width: 50, height: 50) } var element_2: some View { Circle() .frame(width: 50, height: 50) } var element_3: some View { Circle() .frame(width: 50, height: 50) } var body: some View { HStack { Spacer() element_1 Spacer() element_2 Spacer() element_3 Spacer() } } }
这个实现能适配iPad任意尺寸,但元素数量增多(比如7个)时,大量重复的Spacer()会让代码可读性变差:
// 7个元素需要8个Spacer的示例: HStack { Spacer() element_1 Spacer() element_2 Spacer() element_3 Spacer() element_4 Spacer() element_5 Spacer() element_6 Spacer() element_7 Spacer() }
我尝试自定义SpacedHStack组件简化调用,但当前实现效果和原方案不符,需要修改,或者有没有无需自定义组件的优雅重构方式?
方案一:修复自定义SpacedHStack组件
原自定义组件存在两个问题:一是无法正确收集多个子视图,二是缺少首尾的Spacer()。修改后的代码如下:
import SwiftUI struct SpacedHStack: View { private let views: [AnyView] // 用@ViewBuilder接收多个子视图并转换为AnyView数组 init(@ViewBuilder content: @escaping () -> some View) { self.views = content().extractViews() } var body: some View { HStack(spacing: 0) { Spacer() // 开头的Spacer ForEach(views.indices, id: \.self) { index in views[index] Spacer() // 每个元素后添加Spacer(包括最后一个) } } } } // 辅助扩展:将View及其子视图转换为AnyView数组 extension View { func extractViews() -> [AnyView] { func extract(from any: Any) -> [AnyView] { if let view = any as? AnyView { return [view] } else if let tupleView = any as? TupleViewProtocol { return tupleView.extractViews() } else if let view = any as? some View { return [AnyView(view)] } return [] } let mirror = Mirror(reflecting: self) for child in mirror.children { if child.label == "content" { return extract(from: child.value) } } return [AnyView(self)] } } protocol TupleViewProtocol { func extractViews() -> [AnyView] } extension TupleView: TupleViewProtocol where T: Tuple { func extractViews() -> [AnyView] { var result = [AnyView]() recursivelyExtract(from: value, into: &result) return result } private func recursivelyExtract(from tuple: T, into result: inout [AnyView]) { switch tuple { case let (head, tail): result.append(AnyView(head)) recursivelyExtract(from: tail, into: &result) default: break } } } // 递归Tuple协议定义 protocol Tuple {} extension Void: Tuple {} struct Pair<First: View, Second: Tuple>: Tuple { let first: First let second: Second } // 扩展TupleView支持多视图初始化 extension TupleView { init<V1: View, V2: View>(_ v1: V1, _ v2: V2) where T == Pair<V1, Pair<V2, Void>> { self.init(value: Pair(v1, Pair(v2, Void()))) } init<V1: View, V2: View, V3: View>(_ v1: V1, _ v2: V2, _ v3: V3) where T == Pair<V1, Pair<V2, Pair<V3, Void>>> { self.init(value: Pair(v1, Pair(v2, Pair(v3, Void())))) } }
修改后即可按预期调用,效果与原实现完全一致:
SpacedHStack { element_1 element_2 element_3 element_4 element_5 element_6 element_7 }
方案二:无需自定义组件的简化实现
如果可以将元素整理为数组,直接用ForEach遍历即可,代码更简洁:
struct ContentView: View { // 将所有元素整理为数组 let elements: [some View] = [ Circle().frame(width: 50, height: 50), Circle().frame(width: 50, height: 50), Circle().frame(width: 50, height: 50), Circle().frame(width: 50, height: 50), Circle().frame(width: 50, height: 50), Circle().frame(width: 50, height: 50), Circle().frame(width: 50, height: 50) ] var body: some View { HStack(spacing: 0) { Spacer() ForEach(elements.indices, id: \.self) { index in elements[index] Spacer() } } } }
这种方式无需自定义组件,利用SwiftUI原生API即可实现与原方案完全一致的效果,代码可读性和维护性都更强。
内容的提问来源于stack exchange,提问作者swiftenjoyer
相关产品推荐
相关产品推荐

