简体   繁体   English

SwiftUI 列出不带 NavigationLink 的披露指示器

[英]SwiftUI List disclosure indicator without NavigationLink

I am searching for a solution to show the disclosure indicator chevron without having the need to wrap my view into an NavigationLink .我正在寻找一种解决方案来显示披露指示符 chevron 而无需将我的视图包装到NavigationLink中。 For example I want to show the indicator but not navigate to a new view but instead show a modal for example.例如,我想显示指示器但不导航到新视图,而是显示模式。

I have found a lot solutions that hide the indicator button but none which explains how to add one.我找到了很多隐藏指示器按钮的解决方案,但没有一个可以解释如何添加一个。 Is this even possible in the current SwiftUI version?在当前的 SwiftUI 版本中,这甚至可能吗?

struct MyList: View {
    var body: some View {
        NavigationView {
        List {
            Section {
                Text("Item 1")
                Text("Item 2")
                Text("Item 3")
                Text("Item 4")

            }
        }
    }
}

For example I want to add the disclosure indicator to Item 1 without needing to wrap it into an NavigationLink例如,我想将披露指示符添加到Item 1 ,而无需将其包装到NavigationLink

I already tried to fake the indicator with the chevron.right SF Symbol, but the symbol does not match 100% the default iOS one.我已经尝试使用chevron.right SF 符号来伪造指标,但该符号与默认的 iOS 符号 100% 不匹配。 Top is default bottom is chevron.right .顶部是默认底部是chevron.right

披露按钮图像

Hopefully, this is what you are looking for.希望这是您正在寻找的。 You can add the item to a HStack and with a Spacer in between fake it that its a Link:您可以将项目添加到 HStack 并在之间使用 Spacer 假装它是一个链接:

HStack {
                    Text("Item 1")
                    Spacer()
                    Button(action: {

                    }){
                        Image(systemName: "chevron.right")
                            .font(.body)
                    }
                }

It is definitely possible.这绝对是可能的。

You can use a combination of Button and a non-functional NavigationLink to achieve what you want.您可以使用Button和非功能NavigationLink的组合来实现您想要的。

Add the following extension on 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() })
   }
}

Then, in your List , you can do something like this for the row:然后,在您的List中,您可以对行执行以下操作:

// ...
ForEach(section.items) { item in
    Button(action: {
        // your custom navigation / action goes here
    }) {
        HStack {
            Text(item.name)
            Spacer()
            NavigationLink.empty
        }
    }
 }
 // ...

The above produces the same result as if you had used a NavigationLink and also highlights / dehighlights the row as expected on interactions.以上产生的结果与您使用NavigationLink相同,并且还按预期在交互中突出显示/取消突出显示该行。

in iOS15 the following is a better match as the other solutions were little too big and not bold enough.在 iOS15 中,以下是更好的匹配,因为其他解决方案有点太大而且不够大胆。 it'll also resize better to different Display scales better than specifying font sizes.与指定字体大小相比,它还可以更好地调整到不同的显示比例。

HStack {
    Text("Label")
    Spacer()
    Image(systemName: "chevron.forward")
      .font(Font.system(.caption).weight(.bold))
      .foregroundColor(Color(UIColor.tertiaryLabel))
}

Would be good if there was an offical way of doing this.如果有一种官方的方式来做到这一点会很好。 Updating every OS tweak is annoying.更新每个操作系统调整很烦人。

Or maybe create a fake one and use it, even if you tap you can call your events.或者也许创建一个假的并使用它,即使你点击你也可以调用你的事件。

 NavigationLink(destination: EmptyView()) {
            HStack {
                Circle()
                 Text("TITLE")  
               
            }
        }
        .contentShape(Rectangle())
        .onTapGesture {
            print("ALERT MAYBE")
        }

I found an original looking solution.我找到了一个原始的解决方案。 Inserting the icon by hand does not bring the exact same look.手动插入图标不会带来完全相同的外观。

The trick is to use the initializer with the "isActive" parameter and pass a local binding which is always false.诀窍是使用带有“isActive”参数的初始化程序并传递一个始终为假的本地绑定。 So the NavigationLink waits for a programmatically trigger event which will never occur.所以 NavigationLink 等待一个永远不会发生的以编程方式触发的事件。

// use this initializer
NavigationLink(isActive: <Binding<Bool>>, destination: <() -> _>, label: <() -> _>)

You can pass an empty closure to the destination parameter.您可以将空闭包传递给目标参数。 It will never get called anyway.无论如何,它永远不会被调用。 To do some action you put a button on top within a ZStack.要执行一些操作,您可以在 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
        }
    }
}

For accessibility you might need to mimic UIKit version of disclosure indicator.对于可访问性,您可能需要模仿UIKit版本的披露指示器。 You don't need to implement it this way per se but if you use eg Appium for testing you might want to have it like this to keep tests succeeding您本身不需要以这种方式实现它,但如果您使用例如 Appium 进行测试,您可能希望这样来保持测试成功

Apparently UIKit 's disclosure indicator is a disabled button with some accessibility values so here's the solution:显然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)
    }
}

The answers already submitted don't account for one thing: the highlighting of the cell when it is tapped.已经提交的答案并没有说明一件事:点击时单元格的突出显示。 See the About Peek-a-View cell in the image at the bottom of my answer — it is being highlighted because I was pressing it when the screenshot was taken.请参阅我的答案底部图像中的About Peek-a-View单元格 - 它被突出显示,因为我在截取屏幕截图时按下了它。

My solution accounts for both this and the chevron:我的解决方案同时考虑了这一点雪佛龙:

Button(action: { /* handle the tap here */ }) {
    NavigationLink("Cell title", destination: EmptyView())
}
.foregroundColor(Color(uiColor: .label))

The presence of the Button seems to inform SwiftUI when the cell is being tapped;当单元被点击时, Button的存在似乎会通知 SwiftUI; simply adding an onTapGesture() is not enough.仅仅添加一个onTapGesture()是不够的。

The only downside to this approach is that specifying the .foregroundColor() is required;这种方法的唯一缺点是需要指定.foregroundColor() without it, the button text will be blue instead.没有它,按钮文本将改为蓝色。

突出显示一个单元格的 SwiftUI 列表的屏幕截图

I created a custom NavigationLink that:我创建了一个自定义NavigationLink

  1. Adds an action API (instead of having to push a View )添加一个action API (而不是必须推送一个View
  2. Shows the disclosure indicator显示披露指标
  3. Ensures that List cell selection remains as-is确保List单元格选择保持原样

Usage用法

MYNavigationLink(action: {
  didSelectCell()
}) {
  MYCellView()
}

Code代码

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:
// - https://stackoverflow.com/a/66891173/826435
private extension NavigationLink where Label == EmptyView, Destination == EmptyView {
   static var empty: NavigationLink {
       self.init(destination: EmptyView(), label: { EmptyView() })
   }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM