没有 NavigationLink 的 SwiftUI 列表披露指示器
SwiftUI List disclosure indicator without NavigationLink
我正在寻找一种解决方案,无需将我的视图包装到 NavigationLink
中即可显示披露指示器人字形。例如,我想显示指示器但不导航到新视图,而是显示一个模式。
我发现了很多隐藏指示器按钮的解决方案,但 none 解释了如何添加一个。这在当前的 SwiftUI 版本中甚至可能吗?
struct MyList: View {
var body: some View {
NavigationView {
List {
Section {
Text("Item 1")
Text("Item 2")
Text("Item 3")
Text("Item 4")
}
}
}
}
例如,我想将披露指示符添加到 Item 1
而无需将其包装到 NavigationLink
我已经尝试使用 chevron.right
SF 符号来伪造指标,但该符号与默认的 iOS 符号并不 100% 匹配。顶部是默认底部是 chevron.right
.
希望这就是您要找的。您可以将项目添加到 HStack 并在两者之间使用 Spacer 假装它是 Link:
HStack {
Text("Item 1")
Spacer()
Button(action: {
}){
Image(systemName: "chevron.right")
.font(.body)
}
}
或者创建一个假的并使用它,即使您点击也可以调用您的事件。
NavigationLink(destination: EmptyView()) {
HStack {
Circle()
Text("TITLE")
}
}
.contentShape(Rectangle())
.onTapGesture {
print("ALERT MAYBE")
}
绝对有可能。
您可以结合使用 Button
和非功能性 NavigationLink
来实现您想要的效果。
在 NavigationLink
上添加以下扩展名。
extension NavigationLink where Label == EmptyView, Destination == EmptyView {
/// Useful in cases where a `NavigationLink` is needed but there should not be
/// a destination. e.g. for programmatic navigation.
static var empty: NavigationLink {
self.init(destination: EmptyView(), label: { EmptyView() })
}
}
然后,在您的 List
中,您可以对行执行如下操作:
// ...
ForEach(section.items) { item in
Button(action: {
// your custom navigation / action goes here
}) {
HStack {
Text(item.name)
Spacer()
NavigationLink.empty
}
}
}
// ...
上面的结果与您使用 NavigationLink
时产生的结果相同,并且还按预期在交互中突出显示/取消突出显示该行。
在 iOS15 下面是一个更好的匹配,因为其他解决方案有点太大而且不够大胆。与指定字体大小相比,它还可以更好地适应不同的显示比例。
HStack {
Text("Label")
Spacer()
Image(systemName: "chevron.forward")
.font(Font.system(.caption).weight(.bold))
.foregroundColor(Color(UIColor.tertiaryLabel))
}
要是有官方的方法就好了。更新每个 OS 调整很烦人。
我找到了一个看起来很原始的解决方案。手动插入图标不会带来完全相同的外观。
诀窍是使用带有“isActive”参数的初始值设定项并传递始终为 false 的本地绑定。因此 NavigationLink 等待一个永远不会发生的编程触发事件。
// use this initializer
NavigationLink(isActive: <Binding<Bool>>, destination: <() -> _>, label: <() -> _>)
您可以将空闭包传递给目标参数。它永远不会被调用。要执行某些操作,您可以在 ZStack 的顶部放置一个按钮。
func navigationLinkStyle() -> some View {
let never = Binding<Bool> { false } set: { _ in }
return ZStack {
NavigationLink(isActive: never, destination: { }) {
Text("Item 1") // your list cell view
}
Button {
// do your action on tap gesture
} label: {
EmptyView() // invisible placeholder
}
}
}
对于可访问性,您可能需要模仿 UIKit
版本的披露指示器。 您本身不需要以这种方式实现它,但是如果您使用例如用于测试的 Appium 您可能希望像这样保持测试成功
显然 UIKit
的披露指示器是一个具有某些辅助功能值的禁用按钮,因此这是解决方案:
struct DisclosureIndicator: View {
var body: some View {
Button {
} label: {
Image(systemName: "chevron.right")
.font(.body)
.foregroundColor(Color(UIColor.tertiaryLabel))
}
.disabled(true)
.accessibilityLabel(Text("chevron"))
.accessibilityIdentifier("chevron")
.accessibilityHidden(true)
}
}
已提交的答案没有说明一件事:点击单元格时突出显示。请参阅我答案底部图像中的 About Peek-a-View
单元格 - 它被突出显示是因为我在截屏时按下了它。
我的解决方案同时考虑了这个 和 人字形:
Button(action: { /* handle the tap here */ }) {
NavigationLink("Cell title", destination: EmptyView())
}
.foregroundColor(Color(uiColor: .label))
Button
的存在似乎在单元格被点击时通知 SwiftUI;仅添加 onTapGesture()
是不够的。
这种方法的唯一缺点是需要指定 .foregroundColor()
;没有它,按钮文本将改为蓝色。
我创建了一个自定义 NavigationLink
:
- 添加
action
API(而不必推送 View
)
- 显示披露指标
- 确保
List
单元格选择保持 as-is
用法
MYNavigationLink(action: {
didSelectCell()
}) {
MYCellView()
}
代码
import SwiftUI
struct MYNavigationLink<Label: View>: View {
@Environment(\.colorScheme) var colorScheme
private let action: () -> Void
private let label: () -> Label
init(action: @escaping () -> Void, @ViewBuilder label: @escaping () -> Label) {
self.action = action
self.label = label
}
var body: some View {
Button(action: action) {
HStack(spacing: 0) {
label()
Spacer()
NavigationLink.empty
.layoutPriority(-1) // prioritize `label`
}
}
// Fix the `tint` color that `Button` adds
.tint(colorScheme == .dark ? .white : .black) // TODO: Change this for your app
}
}
// Inspiration:
// -
private extension NavigationLink where Label == EmptyView, Destination == EmptyView {
static var empty: NavigationLink {
self.init(destination: EmptyView(), label: { EmptyView() })
}
}
我正在寻找一种解决方案,无需将我的视图包装到 NavigationLink
中即可显示披露指示器人字形。例如,我想显示指示器但不导航到新视图,而是显示一个模式。
我发现了很多隐藏指示器按钮的解决方案,但 none 解释了如何添加一个。这在当前的 SwiftUI 版本中甚至可能吗?
struct MyList: View {
var body: some View {
NavigationView {
List {
Section {
Text("Item 1")
Text("Item 2")
Text("Item 3")
Text("Item 4")
}
}
}
}
例如,我想将披露指示符添加到 Item 1
而无需将其包装到 NavigationLink
我已经尝试使用 chevron.right
SF 符号来伪造指标,但该符号与默认的 iOS 符号并不 100% 匹配。顶部是默认底部是 chevron.right
.
希望这就是您要找的。您可以将项目添加到 HStack 并在两者之间使用 Spacer 假装它是 Link:
HStack {
Text("Item 1")
Spacer()
Button(action: {
}){
Image(systemName: "chevron.right")
.font(.body)
}
}
或者创建一个假的并使用它,即使您点击也可以调用您的事件。
NavigationLink(destination: EmptyView()) {
HStack {
Circle()
Text("TITLE")
}
}
.contentShape(Rectangle())
.onTapGesture {
print("ALERT MAYBE")
}
绝对有可能。
您可以结合使用 Button
和非功能性 NavigationLink
来实现您想要的效果。
在 NavigationLink
上添加以下扩展名。
extension NavigationLink where Label == EmptyView, Destination == EmptyView {
/// Useful in cases where a `NavigationLink` is needed but there should not be
/// a destination. e.g. for programmatic navigation.
static var empty: NavigationLink {
self.init(destination: EmptyView(), label: { EmptyView() })
}
}
然后,在您的 List
中,您可以对行执行如下操作:
// ...
ForEach(section.items) { item in
Button(action: {
// your custom navigation / action goes here
}) {
HStack {
Text(item.name)
Spacer()
NavigationLink.empty
}
}
}
// ...
上面的结果与您使用 NavigationLink
时产生的结果相同,并且还按预期在交互中突出显示/取消突出显示该行。
在 iOS15 下面是一个更好的匹配,因为其他解决方案有点太大而且不够大胆。与指定字体大小相比,它还可以更好地适应不同的显示比例。
HStack {
Text("Label")
Spacer()
Image(systemName: "chevron.forward")
.font(Font.system(.caption).weight(.bold))
.foregroundColor(Color(UIColor.tertiaryLabel))
}
要是有官方的方法就好了。更新每个 OS 调整很烦人。
我找到了一个看起来很原始的解决方案。手动插入图标不会带来完全相同的外观。
诀窍是使用带有“isActive”参数的初始值设定项并传递始终为 false 的本地绑定。因此 NavigationLink 等待一个永远不会发生的编程触发事件。
// use this initializer
NavigationLink(isActive: <Binding<Bool>>, destination: <() -> _>, label: <() -> _>)
您可以将空闭包传递给目标参数。它永远不会被调用。要执行某些操作,您可以在 ZStack 的顶部放置一个按钮。
func navigationLinkStyle() -> some View {
let never = Binding<Bool> { false } set: { _ in }
return ZStack {
NavigationLink(isActive: never, destination: { }) {
Text("Item 1") // your list cell view
}
Button {
// do your action on tap gesture
} label: {
EmptyView() // invisible placeholder
}
}
}
对于可访问性,您可能需要模仿 UIKit
版本的披露指示器。 您本身不需要以这种方式实现它,但是如果您使用例如用于测试的 Appium 您可能希望像这样保持测试成功
显然 UIKit
的披露指示器是一个具有某些辅助功能值的禁用按钮,因此这是解决方案:
struct DisclosureIndicator: View {
var body: some View {
Button {
} label: {
Image(systemName: "chevron.right")
.font(.body)
.foregroundColor(Color(UIColor.tertiaryLabel))
}
.disabled(true)
.accessibilityLabel(Text("chevron"))
.accessibilityIdentifier("chevron")
.accessibilityHidden(true)
}
}
已提交的答案没有说明一件事:点击单元格时突出显示。请参阅我答案底部图像中的 About Peek-a-View
单元格 - 它被突出显示是因为我在截屏时按下了它。
我的解决方案同时考虑了这个 和 人字形:
Button(action: { /* handle the tap here */ }) {
NavigationLink("Cell title", destination: EmptyView())
}
.foregroundColor(Color(uiColor: .label))
Button
的存在似乎在单元格被点击时通知 SwiftUI;仅添加 onTapGesture()
是不够的。
这种方法的唯一缺点是需要指定 .foregroundColor()
;没有它,按钮文本将改为蓝色。
我创建了一个自定义 NavigationLink
:
- 添加
action
API(而不必推送View
) - 显示披露指标
- 确保
List
单元格选择保持 as-is
用法
MYNavigationLink(action: {
didSelectCell()
}) {
MYCellView()
}
代码
import SwiftUI
struct MYNavigationLink<Label: View>: View {
@Environment(\.colorScheme) var colorScheme
private let action: () -> Void
private let label: () -> Label
init(action: @escaping () -> Void, @ViewBuilder label: @escaping () -> Label) {
self.action = action
self.label = label
}
var body: some View {
Button(action: action) {
HStack(spacing: 0) {
label()
Spacer()
NavigationLink.empty
.layoutPriority(-1) // prioritize `label`
}
}
// Fix the `tint` color that `Button` adds
.tint(colorScheme == .dark ? .white : .black) // TODO: Change this for your app
}
}
// Inspiration:
// -
private extension NavigationLink where Label == EmptyView, Destination == EmptyView {
static var empty: NavigationLink {
self.init(destination: EmptyView(), label: { EmptyView() })
}
}