SwiftUI - 向sheet视图传递绑定

3

我需要将一些绑定传递给一个可写的表格。我想出了一个可行的方法,但似乎效率很低。

我重新创建了一个非常简化的代码版本,用作示例。

我有一个自定义的LocationStruct结构体...

struct LocationStruct {
    var id = UUID()
    var name: String
    var location: CLLocationCoordinate2D?
    var haveVisited = false
}

然后我有一个父视图,显示了一些LocationStruct的信息 - 起点、途经点数组和终点...

struct ContentView: View {

    @State var origin = LocationStruct(name: "Paris")
    @State var waypoints = [LocationStruct(name: "Berlin"), LocationStruct(name: "Milan")]
    @State var destination = LocationStruct(name: "Venice")
    
    @State var selectedLocation: Int?
    @State var showSheet = false
    
    var body: some View {
        
        VStack{
            
            HStack{
                Text("Origin:")
                Spacer()
                Text(origin.name)
            }
            .onTapGesture{
                selectedLocation = 1000
                showSheet = true
            }
            
            ForEach(waypoints.indices, id:\.self){ i in
                HStack{
                    Text("Waypoint \(i + 1):")
                    Spacer()
                    Text(waypoints[i].name)
                }
                .onTapGesture{
                    selectedLocation = i
                    showSheet = true
                }
            }
            
            HStack{
                Text("Destination:")
                Spacer()
                Text(destination.name)
            }
            .onTapGesture{
                selectedLocation = 2000
                showSheet = true
            }
        }
        .padding()
        
        .sheet(isPresented: $showSheet){
            LocationSheet(origin: $origin, waypoints: $waypoints, destination: $destination, selectedLocation: $selectedLocation)
        }
    }
    
}

我需要读写ContentView上被点击的位置对象。如果是起点或终点,我会将selectedLocation值设置为1000或2000,否则它将被设置为航路点数组索引(航路点数量有限,因此不会超过1000)。

在很多地方我都不得不重复使用“if let selectedLocation = ...”。有没有更好的方法来做到这一点,也许是某种计算绑定或其他方法?

struct LocationSheet: View {

    @Binding var origin: LocationStruct
    @Binding var waypoints: [LocationStruct]
    @Binding var destination: LocationStruct
    
    @Binding var selectedLocation: Int?
        
    var body: some View {
        
        VStack{
            if let selectedLocation = selectedLocation {
                switch selectedLocation {
                case 1000:
                    TextField("", text: $origin.name).textFieldStyle(.roundedBorder)
                case 2000:
                    TextField("", text: $destination.name).textFieldStyle(.roundedBorder)
                default:
                    TextField("", text: $waypoints[selectedLocation].name).textFieldStyle(.roundedBorder)
                }
            }
            
            Button(action: { markAsVisted() }){
                Text("Visited")
            }
        }
        .padding()
        
    }
    
    func markAsVisted(){
        if let selectedLocation = selectedLocation {
            switch selectedLocation {
            case 1000:
                origin.haveVisited = true
            case 2000:
                destination.haveVisited = true
            default:
                waypoints[selectedLocation].haveVisited = true
            }
        }
    }
    
}

提前致谢

2个回答

1

诀窍在于使用一个非可选的自定义状态结构,它持有isPresented和表格需要的数据,这些数据仅在表格显示时有效。

苹果在Data Essentials in SwiftUI WWDC 2020 at 4:18中提供了一个示例。

EditorConfig 可以在其属性上维护不变量,并可以进行独立测试。而且,由于 EditorConfig 是值类型,对于 EditorConfig 的任何属性(例如进度)的更改都将作为对 EditorConfig 本身的更改可见。

在您的情况下,应按以下方式完成:

struct LocationSheetConfig {
    var selectedLocation: Int = 0 // this is usually a struct, e.g. Location, or an id, e.g. UUID.

    var showSheet = false
 
    mutating func selectLocation(id: Int){
        selectedLocation = id
        showSheet = true
    }

    // usually there is another mutating func for closing the sheet.
}

@State var config = LocationSheetConfig()

.onTapGesture{
    config.selectLocation(id: 1000)
}

.sheet(isPresented: $config.showSheet){
    LocationSheet(origin: $origin, waypoints: $waypoints, destination: $destination, config: $config)
}

我还注意到你的`Location struct`缺少`Identifiable`协议的一致性。并且你不能将索引分配给`ForEach`视图,它必须是一个`Identifiable`结构体的数组,否则当有更改时,它会崩溃(因为它无法跟踪索引,只能跟踪ID)。

0
对于您的问题,不要像那样绑定很多变量,而是可以创建一个EnvironmentObject来存储并仅传递一个屏幕。只使用这个EnvironmentObject将会使您的视图更简单。
创建一个枚举来存储您点击的位置类型,而不是像1000、2000之类的数字常量 - 这将使您的代码不够干净,并且不能很容易地扩展。
枚举和ObservableObject的代码将如下所示。
enum LocationType {
    case origin
    case waypoint
    case destination
}

struct LocationStruct {
    var id = UUID()
    var name: String
    var location: CLLocationCoordinate2D?
    var haveVisited = false
}

class Location : ObservableObject {
    @Published var origin = LocationStruct(name: "Paris")
    @Published var waypoints = [LocationStruct(name: "Berlin"), LocationStruct(name: "Milan")]
    @Published var destination = LocationStruct(name: "Venice")

    @Published var tapLocationType : LocationType = .origin
    @Published var tapIndex : Int?

    func didTapLocaiton(type: LocationType, tapIndex: Int? = nil) {
        self.tapLocationType = type
        self.tapIndex = tapIndex
    }

    func didVisit() {
        switch tapLocationType {
        case .origin:
            origin.haveVisited = true
        case .waypoint:
            waypoints[tapIndex ?? 0].haveVisited = true
        case .destination:
            destination.haveVisited = true
        }
    }

    func getName() -> String {
        switch tapLocationType {
        case .origin:
            return origin.name
        case .waypoint:
            return waypoints[tapIndex ?? 0].name
        case .destination:
            return destination.name
        }
    }
}

然后,当你需要从视图中获取变量时,只需从Location中取出即可。如果你需要更改任何变量,应该从Location对象中调用。这将使你的代码更易于处理。

struct LocationSheet: View {
    @ObservedObject var location : Location

    var body: some View {
        VStack{
            Text(location.getName()).textFieldStyle(.roundedBorder)
            Button(action: { markAsVisted() }){
                Text("Visited")
            }
        }
        .padding()
    }

    func markAsVisted(){
        location.didVisit()
    }
}

struct ContentView: View {
    // make location state for storing
    @StateObject var location = Location()
    @State var showSheet = false

    var body: some View {
        VStack{
            HStack{
                Text("Origin:")
                Spacer()
                Text(location.origin.name)
                Spacer()
                Text(verbatim: "Visit: \(location.origin.haveVisited)")
            }
            .onTapGesture{
                location.didTapLocaiton(type: .origin)
                showSheet = true
            }

            ForEach(location.waypoints.indices, id:\.self){ i in
                HStack{
                    Text("Waypoint \(i + 1):")
                    Spacer()
                    Text(location.waypoints[i].name)
                    Spacer()
                    Text(verbatim: "Visit: \(location.waypoints[i].haveVisited)")
                }
                .onTapGesture{
                    location.didTapLocaiton(type: .waypoint, tapIndex: i)
                    showSheet = true
                }
            }

            HStack{
                Text("Destination:")
                Spacer()
                Text(location.destination.name)
                Spacer()
                Text(verbatim: "Visit: \(location.destination.haveVisited)")
            }
            .onTapGesture{
                location.didTapLocaiton(type: .destination)
                showSheet = true
            }
        }
        .padding()
        .sheet(isPresented: $showSheet){
            LocationSheet(location: location)
        }
    }
}

结果结果


谢谢 - 明显比我的努力要好 - user1830274

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接