前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用 SwiftUI 为 macOS 创建类似于 App Store Connect 的选择器

使用 SwiftUI 为 macOS 创建类似于 App Store Connect 的选择器

原创
作者头像
Swift社区
发布2024-06-25 21:44:51
1060
发布2024-06-25 21:44:51
举报
文章被收录于专栏:Swift社区Swift社区

前言

最近,我一直在为我的应用开发一个全新的界面,它可以让你查看 TestFlight 上所有可用的构建,并允许你将它们添加到测试群组中。

作为这项工作的一部分,我需要创建一个组件,允许用户从特定构建中添加和删除测试群组。我希望构建类似于 App Store Connect 中的选择器组件,使用户体验尽可能熟悉,并在本文中,将展示如何使用 SwiftUI 为 macOS 构建了这个组件。

创建选择器组件

让我们分析一下,我们有一组想要在 SwiftUI 列表中显示的构建。每个构建都包含一组属性,其中之一是 betaGroups,它是一个表示构建所属测试群组的结构体数组。

代码语言:swift
复制
struct VersionBuild: Identifiable, Equatable {
    let number: String
    let date: Date
    let hasAppClip: Bool
    let iconURL: URL?
    let id: String
    let isProcessing: Bool
    var betaGroups: [BetaGroup]
}

struct BetaGroup: Identifiable, Equatable {
    let id: String
    let name: String
}

列表使用一个名为 TestFlightBuildCell 的简单组件来显示构建信息:

代码语言:swift
复制
struct TestFlightBuildCell: View {
    let build: VersionBuild
    
    var body: some View {
        
        HStack(spacing: 12) {
            if let appIcon = build.iconURL {
                KFImage(appIcon)
                    .retry(maxCount: 3, interval: .seconds(5))
                    .cacheOriginalImage()
                    .resizable()
                    .appIconImage(size: .small)
                
            }
            
            VStack(alignment: .leading) {
                
                HStack(alignment: .center) {
                    
                    VStack(alignment:.leading){
                        
                        Text("Build \(build.number)")
                            .font(.HELheadline)
                            .foregroundStyle(.primary)
                            .place(.leading)
                        
                        
                        Text(build.date.fullText)
                            .font(.HELfootnote)
                            .foregroundStyle(.secondary)
                        
                        if build.hasAppClip {
                            Label("Includes App Clip", systemImage: "appclip")
                                .font(.HELfootnote)
                                .foregroundStyle(.secondary)
                        }
                    }
                    
                    Spacer()
                    
                    if build.isProcessing {
                        BuildTag(
                            title: "PROCESSING",
                            background: build.isProcessing ? Color.warning.opacity(0.3) : Color.gray.opacity(0.1)
                        )
                    }
                }
            }
        }
    }
}

在应用程序的上下文中,列表如下所示:

虽然上面的组件可以很好地传达所需的构建信息,但它在应用程序的这个部分仍然缺少一些关键功能。我们需要能够决定构建属于哪些测试群组,并根据需要添加或删除它们。

让我们看看 SwiftUI 中测试群组选择器组件的代码:

代码语言:swift
复制
struct BetaGroupPicker: View {
    // 1
    @Binding var betaGroups: [BetaGroup]
    // 2
    let availableBetaGroups: [BetaGroup]
    // 3
    @State var hoveringGroup: BetaGroup?
    
    var body: some View {
        HStack(spacing: 4) {
            // 4
            ForEach(betaGroups) { betaGroup in
                Text(betaGroup.displayName)
                    .padding(4)
                    .background(Color.gray.opacity(0.2))
                    .bold()
                    .clipShape(Circle())
                    // 5
                    .onHover { hovering in
                        withAnimation {
                            hoveringGroup = hovering ? betaGroup : nil
                        }
                    }
                    // 6
                    .overlay(alignment: .topTrailing) {
                        if hoveringGroup == betaGroup {
                            Button {
                                withAnimation {
                                    betaGroups.removeAll(where: { $0 == betaGroup })
                                }
                            } label: {
                                Image(systemName: "minus.circle.fill")
                                    .foregroundStyle(Color.red)
                            }
                            .buttonStyle(.plain)
                            .offset(x: 2, y: -4)
                        }
                    }
            }
            
            // 7
            if !availableBetaGroups.isEmpty {
                Menu {
                    ForEach(availableBetaGroups) { betaGroup in
                        Button {
                            withAnimation(.snappy) {
                                betaGroups.append(betaGroup)
                            }
                        } label: {
                            Text(betaGroup.name)
                        }
                    }
                } label: {
                    Text(Image(systemName: "plus"))
                        .padding(4)
                        .background(Color.blue.opacity(0.2))
                        .bold()
                        .clipShape(Circle())
                }
                .menuStyle(.button)
                .buttonStyle(.plain)
            }
        }
    }
}

以上代码片段中涉及了很多内容,让我们来逐步分解:

  1. 绑定到构建中可用的测试群组数组。这是一个绑定,因为我们希望能够从内部视图修改它。
  2. 所有可用于添加到构建中的测试群组的数组。父视图负责提供这些信息,正如我们将在下一节中看到的那样。
  3. 一个状态属性,用于跟踪用户悬停的测试群组。此属性的值用于在用户悬停在上面时显示一个移除按钮。
  4. 遍历构建所属的测试群组,并使用 BetaGroup 结构体上的 displayName 属性将它们显示为圆形文本视图。
  5. 当用户悬停在特定测试群组组件上时,修改 hoveringGroup 状态属性。
  6. 使用 .overlay 修改器在用户悬停在测试群组组件上时显示一个移除按钮。该按钮从构建所属的测试群组列表中移除测试群组。
  7. 如果有任何可用的测试群组可以添加到构建中,则显示一个加号按钮,让用户选择要添加的测试群组。

以上代码片段使用了 BetaGroup 结构体上的一个名为 displayName 的属性来显示测试群组的名称,类似于在 App Store Connect 中的显示方式,显示名称中的前两个单词的首字母大写:

代码语言:swift
复制
extension BetaGroup {
    var displayName: String {
        let output = name
            .components(separatedBy: .whitespaces)
            .filter { $0.lowercased() != "and" && $0.lowercased() != "&" }
            .prefix(2)
            .map { $0.first?.uppercased() ?? "" }
            .joined()
        
        return output.isEmpty ? "TF" : output
    }
}

使用选择器组件

现在我们有了 BetaGroupPicker 视图,我们可以开始在 TestFlightBuildCell 组件中使用它,让用户可以从特定构建中添加和删除测试群组:

代码语言:swift
复制
struct TestFlightBuildCell: View {
    @Binding var build: VersionBuild
    let availableBetaGroups: [BetaGroup]

    init(
        build: Binding<VersionBuild>,
        availableBetaGroups: [BetaGroup]
    ) {
        self._build = build
        self.availableBetaGroups = availableBetaGroups.filter { !build.wrappedValue.betaGroups.contains($0) }
    }
    
    var body: some View {
        
        HStack(spacing: 12) {
            if let appIcon = build.iconURL {
                KFImage(appIcon)
                    .retry(maxCount: 3, interval: .seconds(5))
                    .cacheOriginalImage()
                    .resizable()
                    .appIconImage(size: .small)
                
            }
            
            VStack(alignment: .leading) {
                
                HStack(alignment: .center) {
                    
                    VStack(alignment:.leading){
                        
                        Text("Build \(build.number)")
                            .font(.HELheadline)
                            .foregroundStyle(.primary)
                            .place(.leading)
                        
                        
                        Text(build.date.fullText)
                            .font(.HELfootnote)
                            .foregroundStyle(.secondary)
                        
                        if build.hasAppClip {
                            Label("Includes App Clip", systemImage: "appclip")
                                .font(.HELfootnote)
                                .foregroundStyle(.secondary)
                        }
                    }
                    
                    Spacer()
                    
                    if build.isProcessing {
                        BuildTag(
                            title: "PROCESSING",
                            background: build.isProcessing ? Color.warning.opacity(0.3) : Color.gray.opacity(0.1)
                        )
                    }
                }
            }

            BetaGroupPicker(
                betaGroups: $build.betaGroups,
                availableBetaGroups: availableBetaGroups
            )
        }
    }
}

正如你所看到的,使用该组件非常简单。你只需要将父视图上的 build 属性修改为一个绑定,并将可用的测试群组传递给组件。

正如你所看到的,我们编写了一个自定义的初始化方法来过滤出任何已经属于构建的测试群组。

总结

文章介绍了如何使用 SwiftUI为macOS 创建类似于 App Store Connect 的选择器组件。作者在应用程序中添加了一个新的界面,允许用户查看 TestFlight 上所有可用的构建,并将它们添加到测试群组中。为了实现这一功能,作者创建了一个名为 BetaGroupPicker 的组件,该组件允许用户从特定构建中添加和删除测试群组。

BetaGroupPicker 中,用户可以看到构建所属的测试群组,并有选择地将它们添加到或从构建中移除。文章还提供了 TestFlightBuildCell 组件的示例,演示了如何在构建信息中集成 BetaGroupPicker 组件,以便用户可以直接在界面上操作测试群组。通过这一步骤,用户可以更方便地管理测试群组,并为应用程序的测试和部署提供更好的支持。

我正在参与2024腾讯技术创作特训营最新征文,快来和我瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 创建选择器组件
  • 使用选择器组件
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档