返回

SwiftUI 里的 View 树——第三部分:嵌套视图偏好与锚点使用

IOS

SwiftUI-Lab:探究 View 树 part-3 嵌套视图

嵌套视图偏好和锚点入门

在 SwiftUI 中,嵌套视图是一种常见的视图结构,它允许你将多个视图组合在一起形成一个更复杂的视图。嵌套视图可以通过父子关系或通过使用 SwiftUI 的 VStackHStackZStack 等布局视图来实现。

当涉及到嵌套视图时,偏好和锚点是非常有用的工具。偏好允许你从子视图传递信息到父视图,而锚点允许你将子视图定位到父视图内的特定位置。这使得你可以在构建复杂的 UI 布局时具有更大的灵活性。

示例:构建一个带边框的文本字段

为了更好地理解嵌套视图偏好和锚点的用法,让我们构建一个带边框的文本字段。这个文本字段将由一个 TextField 和一个 RoundedRectangle 组成,RoundedRectangle 将作为文本字段的边框。

首先,创建一个新的 SwiftUI 项目并打开 ContentView.swift 文件。

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            // 创建一个文本字段
            TextField("Enter your name", text: /*@START_MENU_TOKEN@*/"Placeholder"/*@END_MENU_TOKEN@*/)
                .padding()
                .background(Color.white)
                .cornerRadius(5)
            
            // 创建一个圆角矩形边框
            RoundedRectangle(cornerRadius: 5)
                .stroke(Color.black, lineWidth: 2)
        }
        .padding()
    }
}

@main
struct SwiftUI_LabApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

运行这段代码,你将看到一个带有圆角边框的文本字段。然而,边框的位置并不是我们想要的,它应该紧贴文本字段。

使用偏好和锚点调整边框位置

为了将边框紧贴文本字段,我们将使用 frame(maxWidth:, maxHeight:) 修饰符和 anchorPreference 函数。

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            // 创建一个文本字段
            TextField("Enter your name", text: /*@START_MENU_TOKEN@*/"Placeholder"/*@END_MENU_TOKEN@*/)
                .padding()
                .background(Color.white)
                .cornerRadius(5)
            
            // 使用 frame 修饰符和 anchorPreference 函数来调整边框位置
            RoundedRectangle(cornerRadius: 5)
                .stroke(Color.black, lineWidth: 2)
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                .anchorPreference(key: FramePreferenceKey.self, value: .bounds, transform: { anchor in
                    return anchor
                })
        }
        .padding()
    }
}

struct FramePreferenceKey: PreferenceKey {
    static var defaultValue: Anchor<CGRect>?

    static func reduce(value: inout Anchor<CGRect>?, nextValue: Anchor<CGRect>?) {
        value = nextValue
    }
}

@main
struct SwiftUI_LabApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

在上面的代码中,我们首先使用 frame(maxWidth:, maxHeight:) 修饰符来设置边框的大小。然后,我们使用 anchorPreference 函数来创建一个键为 FramePreferenceKey 的偏好。这个偏好将把文本字段的边界作为值传递给父视图。

在父视图中,我们使用 onPreferenceChange 修饰符来监听 FramePreferenceKey 的值的变化。当这个值发生变化时,我们将把边框的位置更新为文本字段的边界。

import SwiftUI

struct ContentView: View {
    @State private var frame: Anchor<CGRect>?

    var body: some View {
        VStack {
            // 创建一个文本字段
            TextField("Enter your name", text: /*@START_MENU_TOKEN@*/"Placeholder"/*@END_MENU_TOKEN@*/)
                .padding()
                .background(Color.white)
                .cornerRadius(5)
            
            // 使用 frame 修饰符和 anchorPreference 函数来调整边框位置
            RoundedRectangle(cornerRadius: 5)
                .stroke(Color.black, lineWidth: 2)
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                .anchorPreference(key: FramePreferenceKey.self, value: .bounds, transform: { anchor in
                    return anchor
                })
            
            // 使用 onPreferenceChange 修饰符来监听 FramePreferenceKey 的值的变化
            .onPreferenceChange(FramePreferenceKey.self) { frame in
                self.frame = frame
            }
        }
        .padding()
        
        // 使用 frame 来更新边框的位置
        .overlay(
            GeometryReader { geometry in
                RoundedRectangle(cornerRadius: 5)
                    .stroke(Color.black, lineWidth: 2)
                    .frame(width: geometry.size.width, height: geometry.size.height)
                    .position(x: frame?.x ?? 0, y: frame?.y ?? 0)
            }
        )
    }
}

struct FramePreferenceKey: PreferenceKey {
    static var defaultValue: Anchor<CGRect>?

    static func reduce(value: inout Anchor<CGRect>?, nextValue: Anchor<CGRect>?) {
        value = nextValue
    }
}

@main
struct SwiftUI_LabApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

现在,边框将紧贴文本字段,无论文本字段的大小如何变化。

额外的锚点用法

除了我们在上面示例中使用的 position 锚点之外,SwiftUI 还提供了许多其他的锚点。这些锚点可以用于将子视图定位到父视图内的不同位置。

以下是几个常用的锚点:

  • .topLeading: 将子视图定位到父视图的左上角
  • .top: 将子视图定位到父视图的顶部中央
  • .topTrailing: 将子视图定位到父视图的右上角
  • .leading: 将子视图定位到父视图的左中央
  • .center: 将子视图定位到父视图的中央
  • .trailing: 将子视图定位到父视图的右中央
  • .bottomLeading: 将子视图定位到父视图的左下角
  • .bottom: 将子视图定位到父视图的底部中央
  • .bottomTrailing: 将子视图定位到父视图的右下角

这些锚点可以单独使用,也可以组合使用。例如,你可以使用 .topLeading 锚点将子视图定位到父视图的左上角,然后使用 .trailing 锚点将子视图的右边缘与父视图的右边缘对齐。

总结

在这篇文章中,我们学习了如何在 SwiftUI 中使用嵌套视图偏好和锚点来构建复杂的 UI 布局。我们还了解了一些额外的锚点用法。通过这些知识,你可以构建出更灵活、更美观的 SwiftUI 界面。

我希望这篇文章对你有帮助。如果你有任何问题或建议,请随时留言。