使用多个计算的属性来形成SwiftUI视图的主体

214 阅读3分钟

SwiftUI最强大的特征之一就是它的视图默认是可组合的。View 正因为如此,拆分一个庞大的视图往往只需要将其body ,然后根据需要将任何绑定或其他类型的动作接线。

然而,当我们想拆分一个大的视图以提高其可维护性和可读性时,也有许多其他的技术可以被记住。为了看一看这样的技术,让我们从下面这个ProfileView ,它目前使用一个单一的body 属性来形成其内部的视图层次。

struct ProfileView: View {
    var user: User

    var body: some View {
        ZStack {
            LinearGradient(
                gradient: user.profileGradient,
                startPoint: .top,
                endPoint: .bottom
            )
            .edgesIgnoringSafeArea(.all)

            ScrollView {
                VStack(spacing: 15) {
                    Text(user.name)
                        .font(.title)
                    Text(user.biography)
                        .multilineTextAlignment(.leading)
                }
                .padding()
                .foregroundColor(.white)
            }
        }
    }
}

虽然从行数上看,上面的视图并不庞大,但它绝对可以被拆分,以便随着时间的推移,更容易迭代和扩展其功能。要做到这一点的一个方法是使用前面提到的方法,将其body 属性分割成独立的View 类型--在这种情况下可以实现为私有嵌套类型,以使我们的代码具有整齐的命名间距

private extension ProfileView {
    struct BackgroundView: View {
        var gradient: Gradient

        var body: some View {
            LinearGradient(
                gradient: gradient,
                startPoint: .top,
                endPoint: .bottom
            )
            .edgesIgnoringSafeArea(.all)
        }
    }

    struct InfoView: View {
        var user: User

        var body: some View {
            VStack(spacing: 15) {
                Text(user.name)
                    .font(.title)
                Text(user.biography)
                    .multilineTextAlignment(.leading)
            }
            .padding()
            .foregroundColor(.white)
        }
    }
}

有了上述方法,我们现在可以大大简化我们的主ProfileView ,因为我们现在可以通过在各自的ZStackScrollView 包装器中实例化我们的BackgroundViewInfoView 来实现其body

struct ProfileView: View {
    var user: User

    var body: some View {
        ZStack {
            BackgroundView(gradient: user.profileGradient)
            ScrollView {
                InfoView(user: user)
            }
        }
    }
}

然而,虽然上述方法在很多情况下绝对是一个很好的选择,但在这种情况下,它感觉有点 "沉重"--因为我们既需要定义多个类型来分割我们的代码,又需要将我们的部分模型数据传递到这些嵌套的视图中,这很快就会让人觉得是不必要的忙碌工作。

因此,让我们探索另一种方法,这涉及到为我们视图内部层次结构的不同部分创建额外的body--类似于计算的属性。因为在这种情况下,我们的视图的前景和背景之间有一个非常自然的分离,让我们给我们的属性命名,如果我们使用与SwiftUI的内置body 属性相同的some View 不透明的返回类型,那么我们最终会得到以下的实现。

private extension ProfileView {
    var background: some View {
        LinearGradient(
            gradient: user.profileGradient,
            startPoint: .top,
            endPoint: .bottom
        )
        .edgesIgnoringSafeArea(.all)
    }

    var foreground: some View {
        ScrollView {
            VStack(spacing: 15) {
                Text(user.name)
                    .font(.title)
                Text(user.biography)
                    .multilineTextAlignment(.leading)
            }
            .padding()
            .foregroundColor(.white)
        }
    }
}

有了这个改变,我们现在要做的就是用一个ZStack ,把我们的backgroundforeground 结合起来--这就给了我们一个body 的实现,它基本上是我们的私有实现和SwiftUI这个框架之间的一个 "交接点"。

struct ProfileView: View {
    var user: User

    var body: some View {
        ZStack {
            background
            foreground
        }
    }
}

上述方法的好处是,它不需要我们传递任何额外的数据,因为我们的整个实现现在实际上可以保持在一个单一的类型中--同时仍然实现了我们视图的各个部分之间非常好的分离。

当然,将某些视图分割成完全独立的类型仍然是一个好主意(特别是如果我们想让其中一些部分更容易被重用),但是当我们想分割一个更大的视图,同时让它的实现继续作为一个单元时,上述技术可以很好地被记住。