欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 科技 > 能源 > SwiftUI中的AnyLayout的理解与使用以及自定义Layout

SwiftUI中的AnyLayout的理解与使用以及自定义Layout

2025/4/22 21:31:29 来源:https://blog.csdn.net/guoyongming925/article/details/139415419  浏览:    关键词:SwiftUI中的AnyLayout的理解与使用以及自定义Layout

AnyLayoutSwiftUI中的一个类型擦除容器,它可以包装任何遵循Layout协议的布局。这意味着我们可以使用AnyLayout来抽象具体的布局类型,从而在运行时决定使用哪种布局。这种灵活性极大地增强了UI组件的可重用性和适应性。

AnyLayout可以在保持视图identifier的同时在布局(Layout)之间进行转换。

iOS 16以前的处理

在iOS 16以前,如果如果想要改变布局,或者在横竖屏切换的时候改变布局,我们一般都是通过if判断来处理加载不同布局的视图,比如下面:

struct AnylayoutDemo: View {@Environment(\.horizontalSizeClass) var horizontalSizeClassvar body: some View {VStack {if horizontalSizeClass == .compact {VStack {Text("One")Text("Two")Text("Three")}} else {HStack {Text("One")Text("Two")Text("Three")}}}.font(.largeTitle)}
}

横竖屏切换的时候horizontalSizeClass会变化,进而加载不同布局的视图。在iPhone上不是所有设备的horizontalSizeClass在横竖屏切换的时候都会变化,本文只是示例。

上面代码中horizontalSizeClass变化的时候,SwiftUI渲染if不同分支的代码,这相当于移除一部分组件,又添加了一部分新组件上去。

AnyLayout

引入AnyLayout是为了平滑布局之间的过渡。它是Layout协议的类型擦除实例。使用AnyLayout实例允许我们在不破坏底层子视图状态的情况下改变布局类型。

因为视图层次结构的标识(identifier)始终保持不变,SwiftUI将其视为一个改变的视图,而不是像if-else那样的新视图。

现在将上面的代码采用AnyLayout,如下:

struct AnylayoutDemo: View {@Environment(\.horizontalSizeClass) var horizontalSizeClassvar body: some View {let layout = horizontalSizeClass == .compact ? AnyLayout(VStackLayout()) : AnyLayout(HStackLayout())VStack {layout {Text("One")Text("Two")Text("Three")}}.font(.largeTitle)}
}

上面代码中通过horizontalSizeClass不同的值创建了AnyLayout的实例layout,而该layout代替了HStackVStack

AnyLayout中使用的类型必须符合Layout协议。SwiftUI提供了四个新版本的布局HStackLayout, VStackLayout, ZStackLayoutGridLayout

自定义AnyLayout

SwiftUI中,实现自定义布局涉及到遵循Layout协议。这个协议要求实现下面两个方法。
下面,我将通过一个简单的例子来展示如何创建一个自定义布局,这个布局将会简单地将子视图水平排列,并且在它们之间添加固定的间距。

// 用于计算布局所需的总体尺寸。这个方法需要返回一个 CGSize,表示布局的理想大小。
func sizeThatFits(proposal: ProposedViewSize,subviews: Subviews,cache: inout ()
) -> CGSize
// 用于放置子视图。你需要指定每个子视图的位置。
func placeSubviews(in bounds: CGRect,proposal: ProposedViewSize,subviews: Subviews,cache: inout ()
)

首先,定义一个结构体,让它遵循Layout协议。

struct SimpleHorizontalLayout: Layout {let spacing: CGFloat // 用于定义子视图之间的间距init(spacing: CGFloat) {self.spacing = spacing}
}

这里,SimpleHorizontalLayout结构体有一个spacing属性,用于控制子视图之间的间距。

实现sizeThatFits方法:

func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {var totalWidth: CGFloat = 0var maxHeight: CGFloat = 0for subview in subviews {let subviewSize = subview.sizeThatFits(proposal)totalWidth += subviewSize.widthmaxHeight = max(maxHeight, subviewSize.height)}totalWidth += CGFloat(subviews.count - 1) * spacing // 添加间距return CGSize(width: totalWidth, height: maxHeight)
}

实现placeSubviews方法:

func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {var currentX: CGFloat = bounds.minXfor subview in subviews {let subviewSize = subview.sizeThatFits(proposal)subview.place(at: CGPoint(x: currentX, y: bounds.midY - subviewSize.height / 2), anchor: .topLeading, proposal: ProposedViewSize(width: subviewSize.width, height: subviewSize.height))currentX += subviewSize.width + spacing}
}

最终,在调用的地方:

var body: some View {let layout = AnyLayout(SimpleHorizontalLayout(spacing: 10))layout {Text("One")Text("Two")Text("Three")}.font(.largeTitle)
}

自定义Layout完整代码如下:

struct SimpleHorizontalLayout: Layout {let spacing: CGFloat // 用于定义子视图之间的间距init(spacing: CGFloat) {self.spacing = spacing}func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {var totalWidth: CGFloat = 0var maxHeight: CGFloat = 0for subview in subviews {let subviewSize = subview.sizeThatFits(proposal)totalWidth += subviewSize.widthmaxHeight = max(maxHeight, subviewSize.height)}totalWidth += CGFloat(subviews.count - 1) * spacing // 添加间距return CGSize(width: totalWidth, height: maxHeight)}func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {var currentX: CGFloat = bounds.minXfor subview in subviews {let subviewSize = subview.sizeThatFits(proposal)subview.place(at: CGPoint(x: currentX, y: bounds.midY - subviewSize.height / 2), anchor: .topLeading, proposal: ProposedViewSize(width: subviewSize.width, height: subviewSize.height))currentX += subviewSize.width + spacing}}
}

写在最后

本文主要探索了一下iOS 16以前和以后针对不同布局的一些写法,在iOS 16及以后,我们可以使用AnyLayout做界面的动态布局,整体上都好于iOS 16以前的写法,此外,我们也可以自定义Layout,实现我们想要的布局。

最后,希望能够帮助到有需要的朋友,如果您觉得有帮助,还望点个赞,添加个关注,笔者也会不断地努力,写出更多更好用的文章。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词