Swiftui 带信息按钮的快捷方式列表行

Swiftui 带信息按钮的快捷方式列表行,swiftui,swiftui-navigationlink,swiftui-navigationview,Swiftui,Swiftui Navigationlink,Swiftui Navigationview,UIKit用于支持启用蓝色信息/披露按钮的TableView单元格。以下内容是在SwiftUI中生成的,但是,对于初学者来说,如何使底层功能正常工作是一个挑战 由以下代码生成: struct Session: Identifiable { let date: Date let dir: String let instrument: String let description: String var id: Date { date } } final

UIKit用于支持启用蓝色信息/披露按钮的TableView单元格。以下内容是在SwiftUI中生成的,但是,对于初学者来说,如何使底层功能正常工作是一个挑战

由以下代码生成:

struct Session: Identifiable {
    let date: Date
    let dir: String
    let instrument: String
    let description: String
    var id: Date { date }
}

final class SessionsData: ObservableObject {
    @Published var sessions: [Session]
        
    init() {
        sessions = [Session(date: SessionsData.dateFromString(stringDate: "2016-04-14T10:44:00+0000"),dir:"Rhubarb", instrument:"LCproT", description: "brief Description"),
                    Session(date: SessionsData.dateFromString(stringDate: "2017-04-14T10:44:00+0001"),dir:"Custard", instrument:"LCproU", description: "briefer Description"),
                    Session(date: SessionsData.dateFromString(stringDate: "2018-04-14T10:44:00+0002"),dir:"Jelly", instrument:"LCproV", description: " Description")
        ]
    }
    static func dateFromString(stringDate: String) -> Date {
        let dateFormatter = DateFormatter()
        dateFormatter.locale = Locale(identifier: "en_US_POSIX") // set locale to reliable US_POSIX
        dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
        return dateFormatter.date(from:stringDate)!
    }
}

struct SessionList: View {
    @EnvironmentObject private var sessionData: SessionsData
    
    var body: some View {
        NavigationView {
            List {
                ForEach(sessionData.sessions) { session in
                    SessionRow(session: session )
                }
            }
            .navigationTitle("Session data")
        }
        // without this style modification we get all sorts of UIKit warnings
        .navigationViewStyle(StackNavigationViewStyle())
    }
}

struct SessionRow: View {
    var session: Session
    
    @State private var presentDescription = false
    
    var body: some View {
        HStack(alignment: .center){
            VStack(alignment: .leading) {
                Text(session.dir)
                    .font(.headline)
                    .truncationMode(.tail)
                    .frame(minWidth: 20)

                Text(session.instrument)
                    .font(.caption)
                    .opacity(0.625)
                    .truncationMode(.middle)
            }
            Spacer()
            // SessionGraph is a place holder for the Graph data.
            NavigationLink(destination: SessionGraph()) {
                // if this isn't an EmptyView then we get a disclosure indicator
                EmptyView()
            }
            // Note: without setting the NavigationLink hidden
            // width to 0 the List width is split 50/50 between the
            // SessionRow and the NavigationLink. Making the NavigationLink
            // width 0 means that SessionRow gets all the space. Howeveer
            // NavigationLink still works
            .hidden().frame(width: 0)

            Button(action: { presentDescription = true
                print("\(session.dir):\(presentDescription)")
            }) {
                Image(systemName: "info.circle")
            }
            .buttonStyle(BorderlessButtonStyle())

            NavigationLink(destination: SessionDescription(),
                           isActive: $presentDescription) {
                EmptyView()
            }
            .hidden().frame(width: 0)
        }
        .padding(.vertical, 4)
    }
}

struct SessionGraph: View {
    var body: some View {
        Text("SessionGraph")
    }
}

struct SessionDescription: View {
    var body: some View {
        Text("SessionDescription")
    }
}
问题在于SessionGraph的导航链接的行为。选择SessionGraph(行的主体)将传播到SessionDescription!因此,在一个不受控制的庄园里,人们开始四处张望

我见过几个解决这个问题的解决方案,但是没有一个能在使用XCode 12.3和iOS 14.3时起作用


有什么想法吗?

当您在列表行的背景中放置
导航链接时,
导航链接仍然可以在点击时激活。即使使用
.buttonStyle(BorderlessButtonStyle())
(在我看来这是个bug)

一种可能的解决方案是将所有导航链接移到列表之外,然后从列表行内部激活它们。为此,我们需要保存激活状态的
@State
变量。然后,我们需要将它们作为
@Binding
传递到子视图,并在点击按钮时激活它们

以下是一个可能的例子:

struct SessionList: View {
    @EnvironmentObject private var sessionData: SessionsData
    
    // create state variables for activating NavigationLinks
    @State private var presentGraph: Session?
    @State private var presentDescription: Session?

    var body: some View {
        NavigationView {
            List {
                ForEach(sessionData.sessions) { session in
                    SessionRow(
                        session: session,
                        presentGraph: $presentGraph,
                        presentDescription: $presentDescription
                    )
                }
            }
            .navigationTitle("Session data")
            // put NavigationLinks outside the List
            .background(
                VStack {
                    presentGraphLink
                    presentDescriptionLink
                }
            )
        }
        .navigationViewStyle(StackNavigationViewStyle())
    }
    
    @ViewBuilder
    var presentGraphLink: some View {
        // custom binding to activate a NavigationLink - basically when `presentGraph` is set
        let binding = Binding<Bool>(
            get: { presentGraph != nil },
            set: { if !$0 { presentGraph = nil } }
        )
        // activate the `NavigationLink` when the `binding` is `true` 
        NavigationLink("", destination: SessionGraph(), isActive: binding)
    }
    
    @ViewBuilder
    var presentDescriptionLink: some View {
        let binding = Binding<Bool>(
            get: { presentDescription != nil },
            set: { if !$0 { presentDescription = nil } }
        )
        NavigationLink("", destination: SessionDescription(), isActive: binding)
    }
}

很有效,谢谢。使用我需要理解的代码!您建议如何将所选会话传递给任一子视图?我必须创建defaultSession来处理presentGraph或presentDescription为零的情况,因此??默认的会话传递给每个子视图的构造函数。代码中的@ USS2196409没有传递任何代码给<代码> SeaStase也不<代码> SeaScript描述< /代码>,所以我没有考虑这个场景。最简单的方法是在子视图中设置
var session:session?
。很抱歉,因为我确信您有更好的事情要做,但是任何关于这段代码如何工作的见解都是非常好的。我没有得到会话链接的匹配。或者一些推荐阅读。@user2196409当然,我添加了更多细节。我希望现在更清楚了。链接与会话的匹配是一种知道单击了哪一行以及应该将哪一个会话传递给子视图(如果需要)的方法。嗯!这仍然有点神奇。我猜关键是这个init-Binding(get:escaping()->Value,set:escaping(Value)->Void),它“监视”会话值。因此,当任一会话无导航链接被触发。我有很多东西要学!谢谢
struct SessionRow: View {
    var session: Session

    // pass variables as `@Binding`...
    @Binding var presentGraph: Session?
    @Binding var presentDescription: Session?

    var body: some View {
        HStack {
            Button {
                presentGraph = session // ...and activate them manually
            } label: {
                VStack(alignment: .leading) {
                    Text(session.dir)
                        .font(.headline)
                        .truncationMode(.tail)
                        .frame(minWidth: 20)

                    Text(session.instrument)
                        .font(.caption)
                        .opacity(0.625)
                        .truncationMode(.middle)
                }
            }
            .buttonStyle(PlainButtonStyle())
            Spacer()
            Button {
                presentDescription = session
                print("\(session.dir):\(presentDescription)")
            } label: {
                Image(systemName: "info.circle")
            }
            .buttonStyle(PlainButtonStyle())
        }
        .padding(.vertical, 4)
    }
}