SwiftUI @Query动态排序扩展篇:传递绑定变量
SwiftUI @Query动态排序扩展篇:传递绑定变量

SwiftUI @Query动态排序扩展篇:传递绑定变量

本篇文章作为《Swift UI利用@Query动态切换排序》扩展篇,主要涉及@Query动态排序列表时,传递并绑定变量,以实现外部视图与排序视图之间的变量绑定。

排序示例

struct ProspectsView: View {
    @Environment(\.modelContext) var modelContext
    @State private var isShowingScanner = false
    @State private var selectedProspects = Set<Prospect>()
    @Query(sort: $sortDescriptor) var prospects: [Prospect]
    let filter: FilterType
    
    var body: some View {
        NavigationStack {
            List(prospects, selection: $selectedProspects) { prospect in
                NavigationLink(destination: EditView(prosect:prospect)) {
                    VStack(alignment: .leading) {
                        Text(prospect.name)
                            .font(.headline)
                        Text(prospect.emailAddress)
                            .foregroundStyle(.secondary)
                    }
                }
            }
            .navigationTitle(title)
        }
        .sheet(isPresented: $isShowingScanner) {
            CodeScannerView(codeTypes: [.qr], simulatedData: "Paul Hudson\npaul@hackingwithswift.com", completion: handleScan)
        }
    }
}

在ProspectsView视图中,尝试对List进行动态排序。

因为@Query不支持动态绑定到@State属性,因此需要将列表放到一个排序视图当中,利用构造方法传递排序规则,相关知识可以阅读《Swift UI利用@Query动态切换排序》。

因此,创建了一个排序视图UserView:

import SwiftUI

struct UserView: View {
    var body: some View {
        
    }
}

将SwiftData导入到排序视图中,然后将需要动态排序的列表剪切进来:

import SwiftUI
import SwiftData

struct UserView: View {
    @Environment(\.modelContext) var modelContext
    @Query var prospects: [Prospect]
    
    var body: some View {
        List(prospects, selection: $selectedProspects) { prospect in
            NavigationLink(destination: EditView(prosect:prospect)) {
                VStack(alignment: .leading) {
                    Text(prospect.name)
                        .font(.headline)
                    Text(prospect.emailAddress)
                        .foregroundStyle(.secondary)
                }
            }
        }
    }
}

现在通过构造方法调整@Query的排序方式,以完成动态排序,在外部的ProspectsView视图中定义一个排序变量:

@State private var nameSore = false

设置两个排序按钮,放在toolbar中,根据按钮调整nameSore的布尔值:

ToolbarItem(placement: .topBarTrailing) {
    Menu("排序") {
        Button("姓名排序") {
            nameSore = true
        }
        Button("最近排序") {
            nameSore = false
        }
    }
}

将nameSore传递到列表中,以初始化排序方式:

init(nameSort:Bool) {
    _prospects = Query(sort: \Prospect.name, order: nameSort == true ? .forward : .reverse)
}

现在存在一个问题,那就是原本列表在ProspectsView视图中使用时,绑定的是selectedPropects,现在迁移到排序视图后,绑定的selectedProspects并没有带过来,导致缺失变量的报错。

简单的做法是将selectedProspects变量也剪切到UserView视图中。

但是外部视图ProspectsView中的delete()方法,toolbar都需要使用selectedProspects,因此剪切到UserView视图的方法也不现实。

因此引出本文的核心内容,在@Query动态排序扩展中,传递绑定变量。

传递绑定变量

因为排序视图的List列表需要绑定selectedProspects数组,因此可以将selectedProspects数组传递给UsersView视图。

ProspectsView的selectedProspects数组是一个Set类型:

@State private var selectedProspects = Set<Prospect>()

因此需要在UsersView视图中创建一个@Binding变量:

@Binding var selectedProspects:Set<Prospect>

在UsersView视图中,设置selectedProspects为Binding<Set<Prospect>>,这表示接收的是一个Binding类型,然后赋值给selectedProspects。

init(nameSort:Bool,selectedProspects:Binding<Set<Prospect>>) {
    _prospects = Query(sort: \Prospect.name, order: nameSort == true ? .forward : .reverse)
    self._selectedProspects = selectedProspects
}

通过Binding<>完成传递绑定变量,当nameSort为true时,对列表进行正序排序,反之则倒序排序。

同时,UsersView的List列表绑定的是UsersView中的selectedProspects数组,selectedProspects数组本身由@Binding属性包装器包装,实际绑定的还是PropectsView父视图中的数组。

实现效果

最终的实现效果为,既可以传递绑定变量,完成对SwiftData对象的修改,也可以完成对列表的动态排序。

注意:UserView排序视图的selectedProspects使用.constant进行绑定:

#Preview {
    UserView(nameSort: true, selectedProspects: .constant(Set<Prospect>()))
}

完整代码

ProspectsView代码(父视图)

import SwiftUI
import SwiftData
import CodeScanner

struct ProspectsView: View {
    @Query var prospects: [Prospect]
    @Environment(\.modelContext) var modelContext
    @State private var isShowingScanner = false
    @State private var selectedProspects = Set<Prospect>()
    @State private var hideTips = false
    @State private var nameSore = false
    let filter: FilterType
    
    init(filter: FilterType) {
        self.filter = filter
        
        if filter != .none {
            let showContactedOnly = filter == .contacted
            
            _prospects = Query(filter: #Predicate {
                $0.isContacted == showContactedOnly
            }, sort: [SortDescriptor(\Prospect.name)])
        }
    }
    
    func delete() {
        for prospect in selectedProspects {
            modelContext.delete(prospect)
        }
        selectedProspects=[]
    }
    
    func handleScan(result: Result<ScanResult, ScanError>) {
        isShowingScanner = false
        switch result {
        case .success(let result):
            let details = result.string.components(separatedBy: "\n")
            guard details.count == 2 else { return }
            
            let person = Prospect(name: details[0], emailAddress: details[1], isContacted: false)
            
            modelContext.insert(person)
        case .failure(let error):
            print("Scanning failed: \(error.localizedDescription)")
        }
    }
    
    var title: String {
        switch filter {
        case .none:
            "Everyone"
        case .contacted:
            "Contacted people"
        case .uncontacted:
            "Uncontacted people"
        }
    }
    
    var body: some View {
        NavigationStack {
            if filter == .none,hideTips == false {
                VStack {
                    HStack {
                        Spacer()
                        Image(systemName: "minus.square.fill")
                            .onTapGesture {
                                hideTips = true
                            }
                    }
                    Image(systemName: "lasso.badge.sparkles")
                        .font(.title)
                    Spacer().frame(height: 10)
                    Text("是否联系了潜在客户")
                }
                .foregroundColor(Color.white)
                .frame(width: 200,height: 100)
                .background(Color.blue)
                .cornerRadius(10)
            }
            UserView(nameSort:nameSore,selectedProspects:$selectedProspects)
            .navigationTitle(title)
            .toolbar {
                ToolbarItem(placement: .topBarTrailing) {
                    Button("Scan", systemImage: "qrcode.viewfinder") {
                        isShowingScanner = true
                    }
                }
                ToolbarItem(placement: .topBarLeading) {
                    EditButton()
                }
                ToolbarItem(placement: .topBarTrailing) {
                    Menu("排序") {
                        Button("姓名排序") {
                            nameSore = true
                        }
                        Button("最近排序") {
                            nameSore = false
                        }
                    }
                }
                if selectedProspects.isEmpty == false {
                    ToolbarItem(placement: .bottomBar) {
                        Button("Delete Selected", action: delete)
                    }
                }
            }
        }
        .sheet(isPresented: $isShowingScanner) {
            CodeScannerView(codeTypes: [.qr], simulatedData: "Paul Hudson\npaul@hackingwithswift.com", completion: handleScan)
        }
    }
}
#Preview {
    ProspectsView(filter: .none)
        .modelContainer(for: Prospect.self)
}

UserView代码(排序视图)

import SwiftUI
import SwiftData
struct UserView: View {
    @Environment(\.modelContext) var modelContext
    @Query var prospects: [Prospect]
    @Binding var selectedProspects:Set<Prospect>
    
    init(nameSort:Bool,selectedProspects:Binding<Set<Prospect>>) {
        _prospects = Query(sort: \Prospect.name, order: nameSort == true ? .forward : .reverse)
        self._selectedProspects = selectedProspects
    }
    
    var body: some View {
        List(prospects, selection: $selectedProspects) { prospect in
            NavigationLink(destination: EditView(prosect:prospect)) {
                VStack(alignment: .leading) {
                    Text(prospect.name)
                        .font(.headline)
                    Text(prospect.emailAddress)
                        .foregroundStyle(.secondary)
                }
            }
            .tag(prospect)
            .swipeActions {
                if prospect.isContacted {
                    Button("Mark Uncontacted", systemImage: "person.crop.circle.badge.xmark") {
                        prospect.isContacted.toggle()
                    }
                    .tint(.blue)
                } else {
                    Button("Mark Contacted", systemImage: "person.crop.circle.fill.badge.checkmark") {
                        prospect.isContacted.toggle()
                    }
                    .tint(.green)
                }
                
                Button("Delete", systemImage: "trash", role: .destructive) {
                    modelContext.delete(prospect)
                }
                Button("Remind Me", systemImage: "bell") {
                    addNotification(for: prospect)
                }
                .tint(.orange)
            }
        }
    }
}

#Preview {
    UserView(nameSort: true, selectedProspects: .constant(Set<Prospect>()))
}

   

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开发者。

欢迎加入我们的 微信交流群QQ交流群,交流更多精彩内容!
微信交流群二维码 QQ交流群二维码

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注