将分段样式选择器添加到 SwiftUI 的 NavigationView

Adding Segmented Style Picker to SwiftUI's NavigationView

问题和标题一样简单。我正在尝试将具有 SegmentedPickerStyle 样式的 Picker 放置到 SwiftUI 中的 NavigationBar。它就像本机 Phone 应用程序的历史页面。图片如下

我已经在 Google 和 Github 中查找项目、库或任何教程,但没有成功。我认为如果 nativa 应用程序和 WhatsApp 有它,那么它应该是可能的。任何帮助,将不胜感激。

您可以将Picker 直接放入.navigationBarItems 中。

我遇到的唯一麻烦是让选择器居中。 (只是为了表明选择器确实可以在导航栏中,我用框架和几何组合了一种 hacky 解决方案 Reader。你需要找到一个合适的居中解决方案。)

struct ContentView: View {
    @State private var choices = ["All", "Missed"]
    @State private var choice = 0

    @State private var contacts = [("Anna Lisa Moreno", "9:40 AM"), ("Justin Shumaker", "9:35 AM")]

    var body: some View {
        GeometryReader { geometry in
            NavigationView {
                List {
                    ForEach(self.contacts, id: \.self.0) { (contact, time) in
                        ContactView(name: contact, time: time)
                    }
                    .onDelete(perform: self.deleteItems)
                }
                .navigationBarTitle("Recents")
                .navigationBarItems(
                    leading:
                    HStack {
                        Button("Clear") {
                            // do stuff
                        }
                        Picker(selection: self.$choice, label: Text("Pick One")) {
                            ForEach(0 ..< self.choices.count) {
                                Text(self.choices[[=10=]])
                            }
                        }
                        .frame(width: 130)
                        .pickerStyle(SegmentedPickerStyle())
                            .padding(.leading, (geometry.size.width / 2.0) - 130)
                    },
                trailing: EditButton())
            }
        }
    }

    func deleteItems(at offsets: IndexSet) {
        contacts.remove(atOffsets: offsets)
    }

}

struct ContactView: View {
    var name: String
    var time: String

    var body: some View {
        HStack {
            VStack {
                Image(systemName: "phone.fill.arrow.up.right")
                .font(.headline)
                .foregroundColor(.secondary)
                Text("")
            }
            VStack(alignment: .leading) {
                Text(self.name)
                    .font(.headline)
                Text("iPhone")
                    .foregroundColor(.secondary)
            }
            Spacer()
            Text(self.time)
                .foregroundColor(.secondary)
        }
    }
}

想要让它死点的人,只要在每边放两个HStack,让它们宽度固定且相等。

将此方法添加到 View 扩展。

extension View {
    func navigationBarItems<L, C, T>(leading: L, center: C, trailing: T) -> some View where L: View, C: View, T: View {
        self.navigationBarItems(leading:
            HStack{
                HStack {
                    leading
                }
                .frame(width: 60, alignment: .leading)
                Spacer()
                HStack {
                    center
                }
                 .frame(width: 300, alignment: .center)
                Spacer()
                HStack {
                    //Text("asdasd")
                    trailing
                }
                //.background(Color.blue)
                .frame(width: 100, alignment: .trailing)
            } 
            //.background(Color.yellow)
            .frame(width: UIScreen.main.bounds.size.width-32)
        )
    }
}

现在您有一个 View modifier,其用法与 navigationBatItems(:_) 相同。您可以根据需要编辑代码。

用法示例:

.navigationBarItems(leading: EmptyView(), center:       
    Picker(selection: self.$choice, label: Text("Pick One")) {
        ForEach(0 ..< self.choices.count) {
             Text(self.choices[[=11=]])
        }
     }
    .pickerStyle(SegmentedPickerStyle())
}, trailing: EmptyView())

UPDATE

存在 leading 的问题,尾随项违反了 UINavigationBarContentViewsafeArea。在搜索时,我在这个 answer. It is little helper library called SwiftUIX. If you do not want install whole library -like me- I created a gist 中遇到了另一个解决方案,仅供 navigationBarItems 使用。只需将文件添加到您的项目中。

但不要忘记这一点,它正在拉伸 Picker 以覆盖所有空闲 space 并迫使 StatusView 变窄。所以我不得不像这样设置框架;

.navigationBarItems(center:
    Picker(...) {
        ...
    }
    .frame(width: 150)
, trailing:
    StatusView()
    .frame(width: 70)
)

SwiftUI 2 + 工具栏:

struct DemoView: View {

    @State private var mode: Int = 0

    var body: some View {
        Text("Hello, World!")
            .toolbar {
                ToolbarItem(placement: .principal) {
                    Picker("Color", selection: $mode) {
                        Text("Light").tag(0)
                        Text("Dark").tag(1)
                    }
                    .pickerStyle(SegmentedPickerStyle())
                }
            }
    }
}

如果您需要 segmentcontroll 居中,则需要使用 GeometryReader,下面的代码将提供选择器作为标题和尾随(右)按钮。

你在左右两边设置了两个相同宽度的视图,中间的视图将占据其余部分。 5 是一个神奇的数字,取决于您需要的段的宽度。 您可以进行试验,看看最适合您的。

   GeometryReader {
    
    Text("TEST")
     .navigationBarItems(leading:
                                    HStack {
                                        Spacer().frame(width: geometry.size.width / 5)
                                        Spacer()
                                        picker
                                        Spacer()
                                        Button().frame(width: geometry.size.width / 5)
                                           }.frame(width: geometry.size.width)
    
    }

但更好的解决方案是,如果您保存选择器大小,然后计算其他帧大小,那么选择器在 ipad 和 iphone

上将相同
 @State var segmentControllerWidth: CGFloat = 0

    var body: some View {
            HStack {
                Spacer()
                    .frame(width: (geometry.size.width / 2) - (segmentControllerWidth / 2))
                    .background(Color.red)
                segmentController
                    .fixedSize()
                    .background(PreferenceViewSetter())
                profileButton
                    .frame(width: (geometry.size.width / 2) - (segmentControllerWidth / 2))
            }
            .onPreferenceChange(PreferenceViewKey.self) { preferences in
                segmentControllerWidth = preferences.width
            }
        }
    
    
    struct PreferenceViewSetter: View {
        var body: some View {
            GeometryReader { geometry in
                Rectangle()
                    .fill(Color.clear)
                    .preference(key: PreferenceViewKey.self,
                                value: PreferenceViewData(width: geometry.size.width))
            }
        }
    }
    
    struct PreferenceViewData: Equatable {
        let width: CGFloat
    }
    
    struct PreferenceViewKey: PreferenceKey {
        typealias Value = PreferenceViewData
    
        static var defaultValue = PreferenceViewData(width: 0)
    
        static func reduce(value: inout PreferenceViewData, nextValue: () -> PreferenceViewData) {
            value = nextValue()
        }
    }

简单回答如何将分段控制器居中并隐藏其中一个按钮。

@State var showLeadingButton = true
    var body: some View {
        HStack {
            Button(action: {}, label: {"leading"})
                          .opacity(showLeadingButton ? true : false)


            Spacer()

            Picker(selection: $selectedStatus,
                   label: Text("SEGMENT") {
                segmentValues
                }
             .id(UUID())
             .pickerStyle(SegmentedPickerStyle())
             .fixedSize()

             Spacer()
             Button(action: {}, label: {"trailing"})
        }
        .frame(width: UIScreen.main.bounds.width)
    }